@angular/cdk 18.1.1 → 18.2.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/a11y/index.d.ts +283 -2
- package/coercion/private/index.d.ts +9 -0
- package/drag-drop/index.d.ts +12 -1
- package/esm2022/a11y/key-manager/list-key-manager.mjs +18 -38
- package/esm2022/a11y/key-manager/noop-tree-key-manager.mjs +94 -0
- package/esm2022/a11y/key-manager/tree-key-manager-strategy.mjs +9 -0
- package/esm2022/a11y/key-manager/tree-key-manager.mjs +345 -0
- package/esm2022/a11y/key-manager/typeahead.mjs +91 -0
- package/esm2022/a11y/public-api.mjs +4 -1
- package/esm2022/coercion/private/index.mjs +9 -0
- package/esm2022/coercion/private/observable.mjs +19 -0
- package/esm2022/coercion/private/private_public_index.mjs +5 -0
- package/esm2022/drag-drop/directives/drag.mjs +16 -3
- package/esm2022/drag-drop/drag-ref.mjs +8 -2
- package/esm2022/drag-drop/sorting/single-axis-sort-strategy.mjs +4 -3
- package/esm2022/tree/control/base-tree-control.mjs +7 -2
- package/esm2022/tree/control/flat-tree-control.mjs +8 -2
- package/esm2022/tree/control/nested-tree-control.mjs +11 -2
- package/esm2022/tree/control/tree-control.mjs +1 -1
- package/esm2022/tree/nested-node.mjs +6 -15
- package/esm2022/tree/padding.mjs +2 -4
- package/esm2022/tree/toggle.mjs +15 -8
- package/esm2022/tree/tree-errors.mjs +7 -6
- package/esm2022/tree/tree.mjs +817 -63
- package/esm2022/version.mjs +1 -1
- package/fesm2022/a11y.mjs +520 -40
- package/fesm2022/a11y.mjs.map +1 -1
- package/fesm2022/cdk.mjs +1 -1
- package/fesm2022/cdk.mjs.map +1 -1
- package/fesm2022/coercion/private.mjs +19 -0
- package/fesm2022/coercion/private.mjs.map +1 -0
- package/fesm2022/drag-drop.mjs +25 -5
- package/fesm2022/drag-drop.mjs.map +1 -1
- package/fesm2022/tree.mjs +858 -94
- package/fesm2022/tree.mjs.map +1 -1
- package/package.json +9 -3
- package/schematics/ng-add/index.js +1 -1
- package/schematics/ng-add/index.mjs +1 -1
- package/tree/index.d.ts +304 -25
package/fesm2022/tree.mjs
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { SelectionModel, isDataSource } from '@angular/cdk/collections';
|
|
2
|
-
import { isObservable, Subject, BehaviorSubject, of } from 'rxjs';
|
|
3
|
-
import { take, filter, takeUntil, map, distinctUntilChanged } from 'rxjs/operators';
|
|
2
|
+
import { isObservable, Subject, BehaviorSubject, of, combineLatest, EMPTY, concat } from 'rxjs';
|
|
3
|
+
import { take, filter, takeUntil, startWith, tap, switchMap, map, reduce, concatMap, distinctUntilChanged } from 'rxjs/operators';
|
|
4
4
|
import * as i0 from '@angular/core';
|
|
5
|
-
import { InjectionToken, Directive, Inject, Optional, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, ViewChild, ContentChildren,
|
|
6
|
-
import
|
|
5
|
+
import { InjectionToken, Directive, Inject, Optional, inject, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, ViewChild, ContentChildren, EventEmitter, ChangeDetectorRef, booleanAttribute, Output, numberAttribute, NgModule } from '@angular/core';
|
|
6
|
+
import { TREE_KEY_MANAGER } from '@angular/cdk/a11y';
|
|
7
|
+
import * as i1 from '@angular/cdk/bidi';
|
|
8
|
+
import { coerceObservable } from '@angular/cdk/coercion/private';
|
|
7
9
|
|
|
8
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Base tree control. It has basic toggle/expand/collapse operations on a single data node.
|
|
12
|
+
*
|
|
13
|
+
* @deprecated Use one of levelAccessor or childrenAccessor. To be removed in a future version.
|
|
14
|
+
* @breaking-change 21.0.0
|
|
15
|
+
*/
|
|
9
16
|
class BaseTreeControl {
|
|
10
17
|
constructor() {
|
|
11
18
|
/** A selection model with multi-selection to track expansion status. */
|
|
@@ -54,7 +61,13 @@ class BaseTreeControl {
|
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
|
|
57
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Flat tree control. Able to expand/collapse a subtree recursively for flattened tree.
|
|
66
|
+
*
|
|
67
|
+
* @deprecated Use one of levelAccessor or childrenAccessor instead. To be removed in a future
|
|
68
|
+
* version.
|
|
69
|
+
* @breaking-change 21.0.0
|
|
70
|
+
*/
|
|
58
71
|
class FlatTreeControl extends BaseTreeControl {
|
|
59
72
|
/** Construct with flat tree data node functions getLevel and isExpandable. */
|
|
60
73
|
constructor(getLevel, isExpandable, options) {
|
|
@@ -97,7 +110,13 @@ class FlatTreeControl extends BaseTreeControl {
|
|
|
97
110
|
}
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Nested tree control. Able to expand/collapse a subtree recursively for NestedNode type.
|
|
115
|
+
*
|
|
116
|
+
* @deprecated Use one of levelAccessor or childrenAccessor instead. To be removed in a future
|
|
117
|
+
* version.
|
|
118
|
+
* @breaking-change 21.0.0
|
|
119
|
+
*/
|
|
101
120
|
class NestedTreeControl extends BaseTreeControl {
|
|
102
121
|
/** Construct with nested tree function getChildren. */
|
|
103
122
|
constructor(getChildren, options) {
|
|
@@ -107,6 +126,9 @@ class NestedTreeControl extends BaseTreeControl {
|
|
|
107
126
|
if (this.options) {
|
|
108
127
|
this.trackBy = this.options.trackBy;
|
|
109
128
|
}
|
|
129
|
+
if (this.options?.isExpandable) {
|
|
130
|
+
this.isExpandable = this.options.isExpandable;
|
|
131
|
+
}
|
|
110
132
|
}
|
|
111
133
|
/**
|
|
112
134
|
* Expands all dataNodes in the tree.
|
|
@@ -225,18 +247,19 @@ function getTreeMissingMatchingNodeDefError() {
|
|
|
225
247
|
return Error(`Could not find a matching node definition for the provided node data.`);
|
|
226
248
|
}
|
|
227
249
|
/**
|
|
228
|
-
* Returns an error to be thrown when there
|
|
250
|
+
* Returns an error to be thrown when there is no tree control.
|
|
229
251
|
* @docs-private
|
|
230
252
|
*/
|
|
231
253
|
function getTreeControlMissingError() {
|
|
232
|
-
return Error(`Could not find a tree control for the tree.`);
|
|
254
|
+
return Error(`Could not find a tree control, levelAccessor, or childrenAccessor for the tree.`);
|
|
233
255
|
}
|
|
234
256
|
/**
|
|
235
|
-
* Returns an error to be thrown when
|
|
257
|
+
* Returns an error to be thrown when there are multiple ways of specifying children or level
|
|
258
|
+
* provided to the tree.
|
|
236
259
|
* @docs-private
|
|
237
260
|
*/
|
|
238
|
-
function
|
|
239
|
-
return Error(`
|
|
261
|
+
function getMultipleTreeControlsError() {
|
|
262
|
+
return Error(`More than one of tree control, levelAccessor, or childrenAccessor were provided.`);
|
|
240
263
|
}
|
|
241
264
|
|
|
242
265
|
/**
|
|
@@ -257,13 +280,25 @@ class CdkTree {
|
|
|
257
280
|
this._switchDataSource(dataSource);
|
|
258
281
|
}
|
|
259
282
|
}
|
|
260
|
-
constructor(_differs, _changeDetectorRef) {
|
|
283
|
+
constructor(_differs, _changeDetectorRef, _dir) {
|
|
261
284
|
this._differs = _differs;
|
|
262
285
|
this._changeDetectorRef = _changeDetectorRef;
|
|
286
|
+
this._dir = _dir;
|
|
263
287
|
/** Subject that emits when the component has been destroyed. */
|
|
264
288
|
this._onDestroy = new Subject();
|
|
265
289
|
/** Level of nodes */
|
|
266
290
|
this._levels = new Map();
|
|
291
|
+
/** The immediate parents for a node. This is `null` if there is no parent. */
|
|
292
|
+
this._parents = new Map();
|
|
293
|
+
/**
|
|
294
|
+
* Nodes grouped into each set, which is a list of nodes displayed together in the DOM.
|
|
295
|
+
*
|
|
296
|
+
* Lookup key is the parent of a set. Root nodes have key of null.
|
|
297
|
+
*
|
|
298
|
+
* Values is a 'set' of tree nodes. Each tree node maps to a treeitem element. Sets are in the
|
|
299
|
+
* order that it is rendered. Each set maps directly to aria-posinset and aria-setsize attributes.
|
|
300
|
+
*/
|
|
301
|
+
this._ariaSets = new Map();
|
|
267
302
|
// TODO(tinayuangao): Setup a listener for scrolling, emit the calculated view to viewChange.
|
|
268
303
|
// Remove the MAX_VALUE in viewChange
|
|
269
304
|
/**
|
|
@@ -274,12 +309,31 @@ class CdkTree {
|
|
|
274
309
|
start: 0,
|
|
275
310
|
end: Number.MAX_VALUE,
|
|
276
311
|
});
|
|
312
|
+
/**
|
|
313
|
+
* Maintain a synchronous cache of flattened data nodes. This will only be
|
|
314
|
+
* populated after initial render, and in certain cases, will be delayed due to
|
|
315
|
+
* relying on Observable `getChildren` calls.
|
|
316
|
+
*/
|
|
317
|
+
this._flattenedNodes = new BehaviorSubject([]);
|
|
318
|
+
/** The automatically determined node type for the tree. */
|
|
319
|
+
this._nodeType = new BehaviorSubject(null);
|
|
320
|
+
/** The mapping between data and the node that is rendered. */
|
|
321
|
+
this._nodes = new BehaviorSubject(new Map());
|
|
322
|
+
/**
|
|
323
|
+
* Synchronous cache of nodes for the `TreeKeyManager`. This is separate
|
|
324
|
+
* from `_flattenedNodes` so they can be independently updated at different
|
|
325
|
+
* times.
|
|
326
|
+
*/
|
|
327
|
+
this._keyManagerNodes = new BehaviorSubject([]);
|
|
328
|
+
this._keyManagerFactory = inject(TREE_KEY_MANAGER);
|
|
329
|
+
this._viewInit = false;
|
|
277
330
|
}
|
|
278
|
-
|
|
279
|
-
this.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
331
|
+
ngAfterContentInit() {
|
|
332
|
+
this._initializeKeyManager();
|
|
333
|
+
}
|
|
334
|
+
ngAfterContentChecked() {
|
|
335
|
+
this._updateDefaultNodeDefinition();
|
|
336
|
+
this._subscribeToDataChanges();
|
|
283
337
|
}
|
|
284
338
|
ngOnDestroy() {
|
|
285
339
|
this._nodeOutlet.viewContainer.clear();
|
|
@@ -293,19 +347,35 @@ class CdkTree {
|
|
|
293
347
|
this._dataSubscription.unsubscribe();
|
|
294
348
|
this._dataSubscription = null;
|
|
295
349
|
}
|
|
350
|
+
// In certain tests, the tree might be destroyed before this is initialized
|
|
351
|
+
// in `ngAfterContentInit`.
|
|
352
|
+
this._keyManager?.destroy();
|
|
296
353
|
}
|
|
297
|
-
|
|
354
|
+
ngOnInit() {
|
|
355
|
+
this._checkTreeControlUsage();
|
|
356
|
+
this._initializeDataDiffer();
|
|
357
|
+
}
|
|
358
|
+
ngAfterViewInit() {
|
|
359
|
+
this._viewInit = true;
|
|
360
|
+
}
|
|
361
|
+
_updateDefaultNodeDefinition() {
|
|
298
362
|
const defaultNodeDefs = this._nodeDefs.filter(def => !def.when);
|
|
299
363
|
if (defaultNodeDefs.length > 1 && (typeof ngDevMode === 'undefined' || ngDevMode)) {
|
|
300
364
|
throw getTreeMultipleDefaultNodeDefsError();
|
|
301
365
|
}
|
|
302
366
|
this._defaultNodeDef = defaultNodeDefs[0];
|
|
303
|
-
|
|
304
|
-
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Sets the node type for the tree, if it hasn't been set yet.
|
|
370
|
+
*
|
|
371
|
+
* This will be called by the first node that's rendered in order for the tree
|
|
372
|
+
* to determine what data transformations are required.
|
|
373
|
+
*/
|
|
374
|
+
_setNodeTypeIfUnset(nodeType) {
|
|
375
|
+
if (this._nodeType.value === null) {
|
|
376
|
+
this._nodeType.next(nodeType);
|
|
305
377
|
}
|
|
306
378
|
}
|
|
307
|
-
// TODO(tinayuangao): Work on keyboard traversal and actions, make sure it's working for RTL
|
|
308
|
-
// and nested trees.
|
|
309
379
|
/**
|
|
310
380
|
* Switch to the provided data source by resetting the data and unsubscribing from the current
|
|
311
381
|
* render change subscription if one exists. If the data source is null, interpret this by
|
|
@@ -325,11 +395,21 @@ class CdkTree {
|
|
|
325
395
|
}
|
|
326
396
|
this._dataSource = dataSource;
|
|
327
397
|
if (this._nodeDefs) {
|
|
328
|
-
this.
|
|
398
|
+
this._subscribeToDataChanges();
|
|
329
399
|
}
|
|
330
400
|
}
|
|
401
|
+
_getExpansionModel() {
|
|
402
|
+
if (!this.treeControl) {
|
|
403
|
+
this._expansionModel ??= new SelectionModel(true);
|
|
404
|
+
return this._expansionModel;
|
|
405
|
+
}
|
|
406
|
+
return this.treeControl.expansionModel;
|
|
407
|
+
}
|
|
331
408
|
/** Set up a subscription for the data provided by the data source. */
|
|
332
|
-
|
|
409
|
+
_subscribeToDataChanges() {
|
|
410
|
+
if (this._dataSubscription) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
333
413
|
let dataStream;
|
|
334
414
|
if (isDataSource(this._dataSource)) {
|
|
335
415
|
dataStream = this._dataSource.connect(this);
|
|
@@ -340,34 +420,140 @@ class CdkTree {
|
|
|
340
420
|
else if (Array.isArray(this._dataSource)) {
|
|
341
421
|
dataStream = of(this._dataSource);
|
|
342
422
|
}
|
|
343
|
-
if (dataStream) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
423
|
+
if (!dataStream) {
|
|
424
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
425
|
+
throw getTreeNoValidDataSourceError();
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
347
428
|
}
|
|
348
|
-
|
|
349
|
-
|
|
429
|
+
this._dataSubscription = this._getRenderData(dataStream)
|
|
430
|
+
.pipe(takeUntil(this._onDestroy))
|
|
431
|
+
.subscribe(renderingData => {
|
|
432
|
+
this._renderDataChanges(renderingData);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/** Given an Observable containing a stream of the raw data, returns an Observable containing the RenderingData */
|
|
436
|
+
_getRenderData(dataStream) {
|
|
437
|
+
const expansionModel = this._getExpansionModel();
|
|
438
|
+
return combineLatest([
|
|
439
|
+
dataStream,
|
|
440
|
+
this._nodeType,
|
|
441
|
+
// We don't use the expansion data directly, however we add it here to essentially
|
|
442
|
+
// trigger data rendering when expansion changes occur.
|
|
443
|
+
expansionModel.changed.pipe(startWith(null), tap(expansionChanges => {
|
|
444
|
+
this._emitExpansionChanges(expansionChanges);
|
|
445
|
+
})),
|
|
446
|
+
]).pipe(switchMap(([data, nodeType]) => {
|
|
447
|
+
if (nodeType === null) {
|
|
448
|
+
return of({ renderNodes: data, flattenedNodes: null, nodeType });
|
|
449
|
+
}
|
|
450
|
+
// If we're here, then we know what our node type is, and therefore can
|
|
451
|
+
// perform our usual rendering pipeline, which necessitates converting the data
|
|
452
|
+
return this._computeRenderingData(data, nodeType).pipe(map(convertedData => ({ ...convertedData, nodeType })));
|
|
453
|
+
}));
|
|
454
|
+
}
|
|
455
|
+
_renderDataChanges(data) {
|
|
456
|
+
if (data.nodeType === null) {
|
|
457
|
+
this.renderNodeChanges(data.renderNodes);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
// If we're here, then we know what our node type is, and therefore can
|
|
461
|
+
// perform our usual rendering pipeline.
|
|
462
|
+
this._updateCachedData(data.flattenedNodes);
|
|
463
|
+
this.renderNodeChanges(data.renderNodes);
|
|
464
|
+
this._updateKeyManagerItems(data.flattenedNodes);
|
|
465
|
+
}
|
|
466
|
+
_emitExpansionChanges(expansionChanges) {
|
|
467
|
+
if (!expansionChanges) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const nodes = this._nodes.value;
|
|
471
|
+
for (const added of expansionChanges.added) {
|
|
472
|
+
const node = nodes.get(added);
|
|
473
|
+
node?._emitExpansionState(true);
|
|
474
|
+
}
|
|
475
|
+
for (const removed of expansionChanges.removed) {
|
|
476
|
+
const node = nodes.get(removed);
|
|
477
|
+
node?._emitExpansionState(false);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
_initializeKeyManager() {
|
|
481
|
+
const items = combineLatest([this._keyManagerNodes, this._nodes]).pipe(map(([keyManagerNodes, renderNodes]) => keyManagerNodes.reduce((items, data) => {
|
|
482
|
+
const node = renderNodes.get(this._getExpansionKey(data));
|
|
483
|
+
if (node) {
|
|
484
|
+
items.push(node);
|
|
485
|
+
}
|
|
486
|
+
return items;
|
|
487
|
+
}, [])));
|
|
488
|
+
const keyManagerOptions = {
|
|
489
|
+
trackBy: node => this._getExpansionKey(node.data),
|
|
490
|
+
skipPredicate: node => !!node.isDisabled,
|
|
491
|
+
typeAheadDebounceInterval: true,
|
|
492
|
+
horizontalOrientation: this._dir.value,
|
|
493
|
+
};
|
|
494
|
+
this._keyManager = this._keyManagerFactory(items, keyManagerOptions);
|
|
495
|
+
}
|
|
496
|
+
_initializeDataDiffer() {
|
|
497
|
+
// Provide a default trackBy based on `_getExpansionKey` if one isn't provided.
|
|
498
|
+
const trackBy = this.trackBy ?? ((_index, item) => this._getExpansionKey(item));
|
|
499
|
+
this._dataDiffer = this._differs.find([]).create(trackBy);
|
|
500
|
+
}
|
|
501
|
+
_checkTreeControlUsage() {
|
|
502
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
503
|
+
// Verify that Tree follows API contract of using one of TreeControl, levelAccessor or
|
|
504
|
+
// childrenAccessor. Throw an appropriate error if contract is not met.
|
|
505
|
+
let numTreeControls = 0;
|
|
506
|
+
if (this.treeControl) {
|
|
507
|
+
numTreeControls++;
|
|
508
|
+
}
|
|
509
|
+
if (this.levelAccessor) {
|
|
510
|
+
numTreeControls++;
|
|
511
|
+
}
|
|
512
|
+
if (this.childrenAccessor) {
|
|
513
|
+
numTreeControls++;
|
|
514
|
+
}
|
|
515
|
+
if (!numTreeControls) {
|
|
516
|
+
throw getTreeControlMissingError();
|
|
517
|
+
}
|
|
518
|
+
else if (numTreeControls > 1) {
|
|
519
|
+
throw getMultipleTreeControlsError();
|
|
520
|
+
}
|
|
350
521
|
}
|
|
351
522
|
}
|
|
352
523
|
/** Check for changes made in the data and render each change (node added/removed/moved). */
|
|
353
524
|
renderNodeChanges(data, dataDiffer = this._dataDiffer, viewContainer = this._nodeOutlet.viewContainer, parentData) {
|
|
354
525
|
const changes = dataDiffer.diff(data);
|
|
355
|
-
|
|
526
|
+
// Some tree consumers expect change detection to propagate to nodes
|
|
527
|
+
// even when the array itself hasn't changed; we explicitly detect changes
|
|
528
|
+
// anyways in order for nodes to update their data.
|
|
529
|
+
//
|
|
530
|
+
// However, if change detection is called while the component's view is
|
|
531
|
+
// still initing, then the order of child views initing will be incorrect;
|
|
532
|
+
// to prevent this, we only exit early if the view hasn't initialized yet.
|
|
533
|
+
if (!changes && !this._viewInit) {
|
|
356
534
|
return;
|
|
357
535
|
}
|
|
358
|
-
changes
|
|
536
|
+
changes?.forEachOperation((item, adjustedPreviousIndex, currentIndex) => {
|
|
359
537
|
if (item.previousIndex == null) {
|
|
360
538
|
this.insertNode(data[currentIndex], currentIndex, viewContainer, parentData);
|
|
361
539
|
}
|
|
362
540
|
else if (currentIndex == null) {
|
|
363
541
|
viewContainer.remove(adjustedPreviousIndex);
|
|
364
|
-
this._levels.delete(item.item);
|
|
365
542
|
}
|
|
366
543
|
else {
|
|
367
544
|
const view = viewContainer.get(adjustedPreviousIndex);
|
|
368
545
|
viewContainer.move(view, currentIndex);
|
|
369
546
|
}
|
|
370
547
|
});
|
|
548
|
+
// If the data itself changes, but keeps the same trackBy, we need to update the templates'
|
|
549
|
+
// context to reflect the new object.
|
|
550
|
+
changes?.forEachIdentityChange((record) => {
|
|
551
|
+
const newData = record.item;
|
|
552
|
+
if (record.currentIndex != undefined) {
|
|
553
|
+
const view = viewContainer.get(record.currentIndex);
|
|
554
|
+
view.context.$implicit = newData;
|
|
555
|
+
}
|
|
556
|
+
});
|
|
371
557
|
// TODO: change to `this._changeDetectorRef.markForCheck()`, or just switch this component to
|
|
372
558
|
// use signals.
|
|
373
559
|
this._changeDetectorRef.detectChanges();
|
|
@@ -393,21 +579,24 @@ class CdkTree {
|
|
|
393
579
|
* within the data node view container.
|
|
394
580
|
*/
|
|
395
581
|
insertNode(nodeData, index, viewContainer, parentData) {
|
|
582
|
+
const levelAccessor = this._getLevelAccessor();
|
|
396
583
|
const node = this._getNodeDef(nodeData, index);
|
|
584
|
+
const key = this._getExpansionKey(nodeData);
|
|
397
585
|
// Node context that will be provided to created embedded view
|
|
398
586
|
const context = new CdkTreeNodeOutletContext(nodeData);
|
|
587
|
+
parentData ??= this._parents.get(key) ?? undefined;
|
|
399
588
|
// If the tree is flat tree, then use the `getLevel` function in flat tree control
|
|
400
589
|
// Otherwise, use the level of parent node.
|
|
401
|
-
if (
|
|
402
|
-
context.level =
|
|
590
|
+
if (levelAccessor) {
|
|
591
|
+
context.level = levelAccessor(nodeData);
|
|
403
592
|
}
|
|
404
|
-
else if (
|
|
405
|
-
context.level = this._levels.get(parentData) + 1;
|
|
593
|
+
else if (parentData !== undefined && this._levels.has(this._getExpansionKey(parentData))) {
|
|
594
|
+
context.level = this._levels.get(this._getExpansionKey(parentData)) + 1;
|
|
406
595
|
}
|
|
407
596
|
else {
|
|
408
597
|
context.level = 0;
|
|
409
598
|
}
|
|
410
|
-
this._levels.set(
|
|
599
|
+
this._levels.set(key, context.level);
|
|
411
600
|
// Use default tree nodeOutlet, or nested node's nodeOutlet
|
|
412
601
|
const container = viewContainer ? viewContainer : this._nodeOutlet.viewContainer;
|
|
413
602
|
container.createEmbeddedView(node.template, context, index);
|
|
@@ -418,8 +607,427 @@ class CdkTree {
|
|
|
418
607
|
CdkTreeNode.mostRecentTreeNode.data = nodeData;
|
|
419
608
|
}
|
|
420
609
|
}
|
|
421
|
-
|
|
422
|
-
|
|
610
|
+
/** Whether the data node is expanded or collapsed. Returns true if it's expanded. */
|
|
611
|
+
isExpanded(dataNode) {
|
|
612
|
+
return !!(this.treeControl?.isExpanded(dataNode) ||
|
|
613
|
+
this._expansionModel?.isSelected(this._getExpansionKey(dataNode)));
|
|
614
|
+
}
|
|
615
|
+
/** If the data node is currently expanded, collapse it. Otherwise, expand it. */
|
|
616
|
+
toggle(dataNode) {
|
|
617
|
+
if (this.treeControl) {
|
|
618
|
+
this.treeControl.toggle(dataNode);
|
|
619
|
+
}
|
|
620
|
+
else if (this._expansionModel) {
|
|
621
|
+
this._expansionModel.toggle(this._getExpansionKey(dataNode));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/** Expand the data node. If it is already expanded, does nothing. */
|
|
625
|
+
expand(dataNode) {
|
|
626
|
+
if (this.treeControl) {
|
|
627
|
+
this.treeControl.expand(dataNode);
|
|
628
|
+
}
|
|
629
|
+
else if (this._expansionModel) {
|
|
630
|
+
this._expansionModel.select(this._getExpansionKey(dataNode));
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/** Collapse the data node. If it is already collapsed, does nothing. */
|
|
634
|
+
collapse(dataNode) {
|
|
635
|
+
if (this.treeControl) {
|
|
636
|
+
this.treeControl.collapse(dataNode);
|
|
637
|
+
}
|
|
638
|
+
else if (this._expansionModel) {
|
|
639
|
+
this._expansionModel.deselect(this._getExpansionKey(dataNode));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* If the data node is currently expanded, collapse it and all its descendants.
|
|
644
|
+
* Otherwise, expand it and all its descendants.
|
|
645
|
+
*/
|
|
646
|
+
toggleDescendants(dataNode) {
|
|
647
|
+
if (this.treeControl) {
|
|
648
|
+
this.treeControl.toggleDescendants(dataNode);
|
|
649
|
+
}
|
|
650
|
+
else if (this._expansionModel) {
|
|
651
|
+
if (this.isExpanded(dataNode)) {
|
|
652
|
+
this.collapseDescendants(dataNode);
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
this.expandDescendants(dataNode);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Expand the data node and all its descendants. If they are already expanded, does nothing.
|
|
661
|
+
*/
|
|
662
|
+
expandDescendants(dataNode) {
|
|
663
|
+
if (this.treeControl) {
|
|
664
|
+
this.treeControl.expandDescendants(dataNode);
|
|
665
|
+
}
|
|
666
|
+
else if (this._expansionModel) {
|
|
667
|
+
const expansionModel = this._expansionModel;
|
|
668
|
+
expansionModel.select(this._getExpansionKey(dataNode));
|
|
669
|
+
this._getDescendants(dataNode)
|
|
670
|
+
.pipe(take(1), takeUntil(this._onDestroy))
|
|
671
|
+
.subscribe(children => {
|
|
672
|
+
expansionModel.select(...children.map(child => this._getExpansionKey(child)));
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/** Collapse the data node and all its descendants. If it is already collapsed, does nothing. */
|
|
677
|
+
collapseDescendants(dataNode) {
|
|
678
|
+
if (this.treeControl) {
|
|
679
|
+
this.treeControl.collapseDescendants(dataNode);
|
|
680
|
+
}
|
|
681
|
+
else if (this._expansionModel) {
|
|
682
|
+
const expansionModel = this._expansionModel;
|
|
683
|
+
expansionModel.deselect(this._getExpansionKey(dataNode));
|
|
684
|
+
this._getDescendants(dataNode)
|
|
685
|
+
.pipe(take(1), takeUntil(this._onDestroy))
|
|
686
|
+
.subscribe(children => {
|
|
687
|
+
expansionModel.deselect(...children.map(child => this._getExpansionKey(child)));
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/** Expands all data nodes in the tree. */
|
|
692
|
+
expandAll() {
|
|
693
|
+
if (this.treeControl) {
|
|
694
|
+
this.treeControl.expandAll();
|
|
695
|
+
}
|
|
696
|
+
else if (this._expansionModel) {
|
|
697
|
+
const expansionModel = this._expansionModel;
|
|
698
|
+
expansionModel.select(...this._flattenedNodes.value.map(child => this._getExpansionKey(child)));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/** Collapse all data nodes in the tree. */
|
|
702
|
+
collapseAll() {
|
|
703
|
+
if (this.treeControl) {
|
|
704
|
+
this.treeControl.collapseAll();
|
|
705
|
+
}
|
|
706
|
+
else if (this._expansionModel) {
|
|
707
|
+
const expansionModel = this._expansionModel;
|
|
708
|
+
expansionModel.deselect(...this._flattenedNodes.value.map(child => this._getExpansionKey(child)));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/** Level accessor, used for compatibility between the old Tree and new Tree */
|
|
712
|
+
_getLevelAccessor() {
|
|
713
|
+
return this.treeControl?.getLevel?.bind(this.treeControl) ?? this.levelAccessor;
|
|
714
|
+
}
|
|
715
|
+
/** Children accessor, used for compatibility between the old Tree and new Tree */
|
|
716
|
+
_getChildrenAccessor() {
|
|
717
|
+
return this.treeControl?.getChildren?.bind(this.treeControl) ?? this.childrenAccessor;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Gets the direct children of a node; used for compatibility between the old tree and the
|
|
721
|
+
* new tree.
|
|
722
|
+
*/
|
|
723
|
+
_getDirectChildren(dataNode) {
|
|
724
|
+
const levelAccessor = this._getLevelAccessor();
|
|
725
|
+
const expansionModel = this._expansionModel ?? this.treeControl?.expansionModel;
|
|
726
|
+
if (!expansionModel) {
|
|
727
|
+
return of([]);
|
|
728
|
+
}
|
|
729
|
+
const key = this._getExpansionKey(dataNode);
|
|
730
|
+
const isExpanded = expansionModel.changed.pipe(switchMap(changes => {
|
|
731
|
+
if (changes.added.includes(key)) {
|
|
732
|
+
return of(true);
|
|
733
|
+
}
|
|
734
|
+
else if (changes.removed.includes(key)) {
|
|
735
|
+
return of(false);
|
|
736
|
+
}
|
|
737
|
+
return EMPTY;
|
|
738
|
+
}), startWith(this.isExpanded(dataNode)));
|
|
739
|
+
if (levelAccessor) {
|
|
740
|
+
return combineLatest([isExpanded, this._flattenedNodes]).pipe(map(([expanded, flattenedNodes]) => {
|
|
741
|
+
if (!expanded) {
|
|
742
|
+
return [];
|
|
743
|
+
}
|
|
744
|
+
return this._findChildrenByLevel(levelAccessor, flattenedNodes, dataNode, 1);
|
|
745
|
+
}));
|
|
746
|
+
}
|
|
747
|
+
const childrenAccessor = this._getChildrenAccessor();
|
|
748
|
+
if (childrenAccessor) {
|
|
749
|
+
return coerceObservable(childrenAccessor(dataNode) ?? []);
|
|
750
|
+
}
|
|
751
|
+
throw getTreeControlMissingError();
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Given the list of flattened nodes, the level accessor, and the level range within
|
|
755
|
+
* which to consider children, finds the children for a given node.
|
|
756
|
+
*
|
|
757
|
+
* For example, for direct children, `levelDelta` would be 1. For all descendants,
|
|
758
|
+
* `levelDelta` would be Infinity.
|
|
759
|
+
*/
|
|
760
|
+
_findChildrenByLevel(levelAccessor, flattenedNodes, dataNode, levelDelta) {
|
|
761
|
+
const key = this._getExpansionKey(dataNode);
|
|
762
|
+
const startIndex = flattenedNodes.findIndex(node => this._getExpansionKey(node) === key);
|
|
763
|
+
const dataNodeLevel = levelAccessor(dataNode);
|
|
764
|
+
const expectedLevel = dataNodeLevel + levelDelta;
|
|
765
|
+
const results = [];
|
|
766
|
+
// Goes through flattened tree nodes in the `flattenedNodes` array, and get all
|
|
767
|
+
// descendants within a certain level range.
|
|
768
|
+
//
|
|
769
|
+
// If we reach a node whose level is equal to or less than the level of the tree node,
|
|
770
|
+
// we hit a sibling or parent's sibling, and should stop.
|
|
771
|
+
for (let i = startIndex + 1; i < flattenedNodes.length; i++) {
|
|
772
|
+
const currentLevel = levelAccessor(flattenedNodes[i]);
|
|
773
|
+
if (currentLevel <= dataNodeLevel) {
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
if (currentLevel <= expectedLevel) {
|
|
777
|
+
results.push(flattenedNodes[i]);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return results;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Adds the specified node component to the tree's internal registry.
|
|
784
|
+
*
|
|
785
|
+
* This primarily facilitates keyboard navigation.
|
|
786
|
+
*/
|
|
787
|
+
_registerNode(node) {
|
|
788
|
+
this._nodes.value.set(this._getExpansionKey(node.data), node);
|
|
789
|
+
this._nodes.next(this._nodes.value);
|
|
790
|
+
}
|
|
791
|
+
/** Removes the specified node component from the tree's internal registry. */
|
|
792
|
+
_unregisterNode(node) {
|
|
793
|
+
this._nodes.value.delete(this._getExpansionKey(node.data));
|
|
794
|
+
this._nodes.next(this._nodes.value);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* For the given node, determine the level where this node appears in the tree.
|
|
798
|
+
*
|
|
799
|
+
* This is intended to be used for `aria-level` but is 0-indexed.
|
|
800
|
+
*/
|
|
801
|
+
_getLevel(node) {
|
|
802
|
+
return this._levels.get(this._getExpansionKey(node));
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* For the given node, determine the size of the parent's child set.
|
|
806
|
+
*
|
|
807
|
+
* This is intended to be used for `aria-setsize`.
|
|
808
|
+
*/
|
|
809
|
+
_getSetSize(dataNode) {
|
|
810
|
+
const set = this._getAriaSet(dataNode);
|
|
811
|
+
return set.length;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* For the given node, determine the index (starting from 1) of the node in its parent's child set.
|
|
815
|
+
*
|
|
816
|
+
* This is intended to be used for `aria-posinset`.
|
|
817
|
+
*/
|
|
818
|
+
_getPositionInSet(dataNode) {
|
|
819
|
+
const set = this._getAriaSet(dataNode);
|
|
820
|
+
const key = this._getExpansionKey(dataNode);
|
|
821
|
+
return set.findIndex(node => this._getExpansionKey(node) === key) + 1;
|
|
822
|
+
}
|
|
823
|
+
/** Given a CdkTreeNode, gets the node that renders that node's parent's data. */
|
|
824
|
+
_getNodeParent(node) {
|
|
825
|
+
const parent = this._parents.get(this._getExpansionKey(node.data));
|
|
826
|
+
return parent && this._nodes.value.get(this._getExpansionKey(parent));
|
|
827
|
+
}
|
|
828
|
+
/** Given a CdkTreeNode, gets the nodes that renders that node's child data. */
|
|
829
|
+
_getNodeChildren(node) {
|
|
830
|
+
return this._getDirectChildren(node.data).pipe(map(children => children.reduce((nodes, child) => {
|
|
831
|
+
const value = this._nodes.value.get(this._getExpansionKey(child));
|
|
832
|
+
if (value) {
|
|
833
|
+
nodes.push(value);
|
|
834
|
+
}
|
|
835
|
+
return nodes;
|
|
836
|
+
}, [])));
|
|
837
|
+
}
|
|
838
|
+
/** `keydown` event handler; this just passes the event to the `TreeKeyManager`. */
|
|
839
|
+
_sendKeydownToKeyManager(event) {
|
|
840
|
+
this._keyManager.onKeydown(event);
|
|
841
|
+
}
|
|
842
|
+
/** Gets all nested descendants of a given node. */
|
|
843
|
+
_getDescendants(dataNode) {
|
|
844
|
+
if (this.treeControl) {
|
|
845
|
+
return of(this.treeControl.getDescendants(dataNode));
|
|
846
|
+
}
|
|
847
|
+
if (this.levelAccessor) {
|
|
848
|
+
const results = this._findChildrenByLevel(this.levelAccessor, this._flattenedNodes.value, dataNode, Infinity);
|
|
849
|
+
return of(results);
|
|
850
|
+
}
|
|
851
|
+
if (this.childrenAccessor) {
|
|
852
|
+
return this._getAllChildrenRecursively(dataNode).pipe(reduce((allChildren, nextChildren) => {
|
|
853
|
+
allChildren.push(...nextChildren);
|
|
854
|
+
return allChildren;
|
|
855
|
+
}, []));
|
|
856
|
+
}
|
|
857
|
+
throw getTreeControlMissingError();
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Gets all children and sub-children of the provided node.
|
|
861
|
+
*
|
|
862
|
+
* This will emit multiple times, in the order that the children will appear
|
|
863
|
+
* in the tree, and can be combined with a `reduce` operator.
|
|
864
|
+
*/
|
|
865
|
+
_getAllChildrenRecursively(dataNode) {
|
|
866
|
+
if (!this.childrenAccessor) {
|
|
867
|
+
return of([]);
|
|
868
|
+
}
|
|
869
|
+
return coerceObservable(this.childrenAccessor(dataNode)).pipe(take(1), switchMap(children => {
|
|
870
|
+
// Here, we cache the parents of a particular child so that we can compute the levels.
|
|
871
|
+
for (const child of children) {
|
|
872
|
+
this._parents.set(this._getExpansionKey(child), dataNode);
|
|
873
|
+
}
|
|
874
|
+
return of(...children).pipe(concatMap(child => concat(of([child]), this._getAllChildrenRecursively(child))));
|
|
875
|
+
}));
|
|
876
|
+
}
|
|
877
|
+
_getExpansionKey(dataNode) {
|
|
878
|
+
// In the case that a key accessor function was not provided by the
|
|
879
|
+
// tree user, we'll default to using the node object itself as the key.
|
|
880
|
+
//
|
|
881
|
+
// This cast is safe since:
|
|
882
|
+
// - if an expansionKey is provided, TS will infer the type of K to be
|
|
883
|
+
// the return type.
|
|
884
|
+
// - if it's not, then K will be defaulted to T.
|
|
885
|
+
return this.expansionKey?.(dataNode) ?? dataNode;
|
|
886
|
+
}
|
|
887
|
+
_getAriaSet(node) {
|
|
888
|
+
const key = this._getExpansionKey(node);
|
|
889
|
+
const parent = this._parents.get(key);
|
|
890
|
+
const parentKey = parent ? this._getExpansionKey(parent) : null;
|
|
891
|
+
const set = this._ariaSets.get(parentKey);
|
|
892
|
+
return set ?? [node];
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Finds the parent for the given node. If this is a root node, this
|
|
896
|
+
* returns null. If we're unable to determine the parent, for example,
|
|
897
|
+
* if we don't have cached node data, this returns undefined.
|
|
898
|
+
*/
|
|
899
|
+
_findParentForNode(node, index, cachedNodes) {
|
|
900
|
+
// In all cases, we have a mapping from node to level; all we need to do here is backtrack in
|
|
901
|
+
// our flattened list of nodes to determine the first node that's of a level lower than the
|
|
902
|
+
// provided node.
|
|
903
|
+
if (!cachedNodes.length) {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
const currentLevel = this._levels.get(this._getExpansionKey(node)) ?? 0;
|
|
907
|
+
for (let parentIndex = index - 1; parentIndex >= 0; parentIndex--) {
|
|
908
|
+
const parentNode = cachedNodes[parentIndex];
|
|
909
|
+
const parentLevel = this._levels.get(this._getExpansionKey(parentNode)) ?? 0;
|
|
910
|
+
if (parentLevel < currentLevel) {
|
|
911
|
+
return parentNode;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Given a set of root nodes and the current node level, flattens any nested
|
|
918
|
+
* nodes into a single array.
|
|
919
|
+
*
|
|
920
|
+
* If any nodes are not expanded, then their children will not be added into the array.
|
|
921
|
+
* This will still traverse all nested children in order to build up our internal data
|
|
922
|
+
* models, but will not include them in the returned array.
|
|
923
|
+
*/
|
|
924
|
+
_flattenNestedNodesWithExpansion(nodes, level = 0) {
|
|
925
|
+
const childrenAccessor = this._getChildrenAccessor();
|
|
926
|
+
// If we're using a level accessor, we don't need to flatten anything.
|
|
927
|
+
if (!childrenAccessor) {
|
|
928
|
+
return of([...nodes]);
|
|
929
|
+
}
|
|
930
|
+
return of(...nodes).pipe(concatMap(node => {
|
|
931
|
+
const parentKey = this._getExpansionKey(node);
|
|
932
|
+
if (!this._parents.has(parentKey)) {
|
|
933
|
+
this._parents.set(parentKey, null);
|
|
934
|
+
}
|
|
935
|
+
this._levels.set(parentKey, level);
|
|
936
|
+
const children = coerceObservable(childrenAccessor(node));
|
|
937
|
+
return concat(of([node]), children.pipe(take(1), tap(childNodes => {
|
|
938
|
+
this._ariaSets.set(parentKey, [...(childNodes ?? [])]);
|
|
939
|
+
for (const child of childNodes ?? []) {
|
|
940
|
+
const childKey = this._getExpansionKey(child);
|
|
941
|
+
this._parents.set(childKey, node);
|
|
942
|
+
this._levels.set(childKey, level + 1);
|
|
943
|
+
}
|
|
944
|
+
}), switchMap(childNodes => {
|
|
945
|
+
if (!childNodes) {
|
|
946
|
+
return of([]);
|
|
947
|
+
}
|
|
948
|
+
return this._flattenNestedNodesWithExpansion(childNodes, level + 1).pipe(map(nestedNodes => (this.isExpanded(node) ? nestedNodes : [])));
|
|
949
|
+
})));
|
|
950
|
+
}), reduce((results, children) => {
|
|
951
|
+
results.push(...children);
|
|
952
|
+
return results;
|
|
953
|
+
}, []));
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Converts children for certain tree configurations.
|
|
957
|
+
*
|
|
958
|
+
* This also computes parent, level, and group data.
|
|
959
|
+
*/
|
|
960
|
+
_computeRenderingData(nodes, nodeType) {
|
|
961
|
+
// The only situations where we have to convert children types is when
|
|
962
|
+
// they're mismatched; i.e. if the tree is using a childrenAccessor and the
|
|
963
|
+
// nodes are flat, or if the tree is using a levelAccessor and the nodes are
|
|
964
|
+
// nested.
|
|
965
|
+
if (this.childrenAccessor && nodeType === 'flat') {
|
|
966
|
+
// This flattens children into a single array.
|
|
967
|
+
this._ariaSets.set(null, [...nodes]);
|
|
968
|
+
return this._flattenNestedNodesWithExpansion(nodes).pipe(map(flattenedNodes => ({
|
|
969
|
+
renderNodes: flattenedNodes,
|
|
970
|
+
flattenedNodes,
|
|
971
|
+
})));
|
|
972
|
+
}
|
|
973
|
+
else if (this.levelAccessor && nodeType === 'nested') {
|
|
974
|
+
// In the nested case, we only look for root nodes. The CdkNestedNode
|
|
975
|
+
// itself will handle rendering each individual node's children.
|
|
976
|
+
const levelAccessor = this.levelAccessor;
|
|
977
|
+
return of(nodes.filter(node => levelAccessor(node) === 0)).pipe(map(rootNodes => ({
|
|
978
|
+
renderNodes: rootNodes,
|
|
979
|
+
flattenedNodes: nodes,
|
|
980
|
+
})), tap(({ flattenedNodes }) => {
|
|
981
|
+
this._calculateParents(flattenedNodes);
|
|
982
|
+
}));
|
|
983
|
+
}
|
|
984
|
+
else if (nodeType === 'flat') {
|
|
985
|
+
// In the case of a TreeControl, we know that the node type matches up
|
|
986
|
+
// with the TreeControl, and so no conversions are necessary. Otherwise,
|
|
987
|
+
// we've already confirmed that the data model matches up with the
|
|
988
|
+
// desired node type here.
|
|
989
|
+
return of({ renderNodes: nodes, flattenedNodes: nodes }).pipe(tap(({ flattenedNodes }) => {
|
|
990
|
+
this._calculateParents(flattenedNodes);
|
|
991
|
+
}));
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
// For nested nodes, we still need to perform the node flattening in order
|
|
995
|
+
// to maintain our caches for various tree operations.
|
|
996
|
+
this._ariaSets.set(null, [...nodes]);
|
|
997
|
+
return this._flattenNestedNodesWithExpansion(nodes).pipe(map(flattenedNodes => ({
|
|
998
|
+
renderNodes: nodes,
|
|
999
|
+
flattenedNodes,
|
|
1000
|
+
})));
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
_updateCachedData(flattenedNodes) {
|
|
1004
|
+
this._flattenedNodes.next(flattenedNodes);
|
|
1005
|
+
}
|
|
1006
|
+
_updateKeyManagerItems(flattenedNodes) {
|
|
1007
|
+
this._keyManagerNodes.next(flattenedNodes);
|
|
1008
|
+
}
|
|
1009
|
+
/** Traverse the flattened node data and compute parents, levels, and group data. */
|
|
1010
|
+
_calculateParents(flattenedNodes) {
|
|
1011
|
+
const levelAccessor = this._getLevelAccessor();
|
|
1012
|
+
if (!levelAccessor) {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
this._parents.clear();
|
|
1016
|
+
this._ariaSets.clear();
|
|
1017
|
+
for (let index = 0; index < flattenedNodes.length; index++) {
|
|
1018
|
+
const dataNode = flattenedNodes[index];
|
|
1019
|
+
const key = this._getExpansionKey(dataNode);
|
|
1020
|
+
this._levels.set(key, levelAccessor(dataNode));
|
|
1021
|
+
const parent = this._findParentForNode(dataNode, index, flattenedNodes);
|
|
1022
|
+
this._parents.set(key, parent);
|
|
1023
|
+
const parentKey = parent ? this._getExpansionKey(parent) : null;
|
|
1024
|
+
const group = this._ariaSets.get(parentKey) ?? [];
|
|
1025
|
+
group.splice(index, 0, dataNode);
|
|
1026
|
+
this._ariaSets.set(parentKey, group);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTree, deps: [{ token: i0.IterableDiffers }, { token: i0.ChangeDetectorRef }, { token: i1.Directionality }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1030
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.1.0", type: CdkTree, isStandalone: true, selector: "cdk-tree", inputs: { dataSource: "dataSource", treeControl: "treeControl", levelAccessor: "levelAccessor", childrenAccessor: "childrenAccessor", trackBy: "trackBy", expansionKey: "expansionKey" }, host: { attributes: { "role": "tree" }, listeners: { "keydown": "_sendKeydownToKeyManager($event)" }, classAttribute: "cdk-tree" }, queries: [{ propertyName: "_nodeDefs", predicate: CdkTreeNodeDef, descendants: true }], viewQueries: [{ propertyName: "_nodeOutlet", first: true, predicate: CdkTreeNodeOutlet, descendants: true, static: true }], exportAs: ["cdkTree"], ngImport: i0, template: `<ng-container cdkTreeNodeOutlet></ng-container>`, isInline: true, dependencies: [{ kind: "directive", type: CdkTreeNodeOutlet, selector: "[cdkTreeNodeOutlet]" }], changeDetection: i0.ChangeDetectionStrategy.Default, encapsulation: i0.ViewEncapsulation.None }); }
|
|
423
1031
|
}
|
|
424
1032
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTree, decorators: [{
|
|
425
1033
|
type: Component,
|
|
@@ -430,6 +1038,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
|
|
|
430
1038
|
host: {
|
|
431
1039
|
'class': 'cdk-tree',
|
|
432
1040
|
'role': 'tree',
|
|
1041
|
+
'(keydown)': '_sendKeydownToKeyManager($event)',
|
|
433
1042
|
},
|
|
434
1043
|
encapsulation: ViewEncapsulation.None,
|
|
435
1044
|
// The "OnPush" status for the `CdkTree` component is effectively a noop, so we are removing it.
|
|
@@ -440,12 +1049,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
|
|
|
440
1049
|
standalone: true,
|
|
441
1050
|
imports: [CdkTreeNodeOutlet],
|
|
442
1051
|
}]
|
|
443
|
-
}], ctorParameters: () => [{ type: i0.IterableDiffers }, { type: i0.ChangeDetectorRef }], propDecorators: { dataSource: [{
|
|
1052
|
+
}], ctorParameters: () => [{ type: i0.IterableDiffers }, { type: i0.ChangeDetectorRef }, { type: i1.Directionality }], propDecorators: { dataSource: [{
|
|
444
1053
|
type: Input
|
|
445
1054
|
}], treeControl: [{
|
|
446
1055
|
type: Input
|
|
1056
|
+
}], levelAccessor: [{
|
|
1057
|
+
type: Input
|
|
1058
|
+
}], childrenAccessor: [{
|
|
1059
|
+
type: Input
|
|
447
1060
|
}], trackBy: [{
|
|
448
1061
|
type: Input
|
|
1062
|
+
}], expansionKey: [{
|
|
1063
|
+
type: Input
|
|
449
1064
|
}], _nodeOutlet: [{
|
|
450
1065
|
type: ViewChild,
|
|
451
1066
|
args: [CdkTreeNodeOutlet, { static: true }]
|
|
@@ -463,16 +1078,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
|
|
|
463
1078
|
class CdkTreeNode {
|
|
464
1079
|
/**
|
|
465
1080
|
* The role of the tree node.
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
1081
|
+
*
|
|
1082
|
+
* @deprecated This will be ignored; the tree will automatically determine the appropriate role
|
|
1083
|
+
* for tree node. This input will be removed in a future version.
|
|
1084
|
+
* @breaking-change 21.0.0
|
|
469
1085
|
*/
|
|
470
1086
|
get role() {
|
|
471
1087
|
return 'treeitem';
|
|
472
1088
|
}
|
|
473
1089
|
set role(_role) {
|
|
474
|
-
//
|
|
475
|
-
|
|
1090
|
+
// ignore any role setting, we handle this internally.
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Whether or not this node is expandable.
|
|
1094
|
+
*
|
|
1095
|
+
* If not using `FlatTreeControl`, or if `isExpandable` is not provided to
|
|
1096
|
+
* `NestedTreeControl`, this should be provided for correct node a11y.
|
|
1097
|
+
*/
|
|
1098
|
+
get isExpandable() {
|
|
1099
|
+
return this._isExpandable();
|
|
1100
|
+
}
|
|
1101
|
+
set isExpandable(isExpandable) {
|
|
1102
|
+
this._inputIsExpandable = isExpandable;
|
|
1103
|
+
}
|
|
1104
|
+
get isExpanded() {
|
|
1105
|
+
return this._tree.isExpanded(this._data);
|
|
1106
|
+
}
|
|
1107
|
+
set isExpanded(isExpanded) {
|
|
1108
|
+
if (isExpanded) {
|
|
1109
|
+
this.expand();
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
this.collapse();
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
getLabel() {
|
|
1116
|
+
return this.typeaheadLabel || this._elementRef.nativeElement.textContent?.trim() || '';
|
|
476
1117
|
}
|
|
477
1118
|
/**
|
|
478
1119
|
* The most recently created `CdkTreeNode`. We save it in static variable so we can retrieve it
|
|
@@ -486,40 +1127,101 @@ class CdkTreeNode {
|
|
|
486
1127
|
set data(value) {
|
|
487
1128
|
if (value !== this._data) {
|
|
488
1129
|
this._data = value;
|
|
489
|
-
this._setRoleFromData();
|
|
490
1130
|
this._dataChanges.next();
|
|
491
1131
|
}
|
|
492
1132
|
}
|
|
493
|
-
|
|
494
|
-
|
|
1133
|
+
/* If leaf node, return true to not assign aria-expanded attribute */
|
|
1134
|
+
get isLeafNode() {
|
|
1135
|
+
// If flat tree node data returns false for expandable property, it's a leaf node
|
|
1136
|
+
if (this._tree.treeControl?.isExpandable !== undefined &&
|
|
1137
|
+
!this._tree.treeControl.isExpandable(this._data)) {
|
|
1138
|
+
return true;
|
|
1139
|
+
// If nested tree node data returns 0 descendants, it's a leaf node
|
|
1140
|
+
}
|
|
1141
|
+
else if (this._tree.treeControl?.isExpandable === undefined &&
|
|
1142
|
+
this._tree.treeControl?.getDescendants(this._data).length === 0) {
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
return false;
|
|
495
1146
|
}
|
|
496
1147
|
get level() {
|
|
497
|
-
// If the
|
|
1148
|
+
// If the tree has a levelAccessor, use it to get the level. Otherwise read the
|
|
498
1149
|
// aria-level off the parent node and use it as the level for this node (note aria-level is
|
|
499
1150
|
// 1-indexed, while this property is 0-indexed, so we don't need to increment).
|
|
500
|
-
return this._tree.
|
|
501
|
-
|
|
502
|
-
|
|
1151
|
+
return this._tree._getLevel(this._data) ?? this._parentNodeAriaLevel;
|
|
1152
|
+
}
|
|
1153
|
+
/** Determines if the tree node is expandable. */
|
|
1154
|
+
_isExpandable() {
|
|
1155
|
+
if (this._tree.treeControl) {
|
|
1156
|
+
if (this.isLeafNode) {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
// For compatibility with trees created using TreeControl before we added
|
|
1160
|
+
// CdkTreeNode#isExpandable.
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
return this._inputIsExpandable;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Determines the value for `aria-expanded`.
|
|
1167
|
+
*
|
|
1168
|
+
* For non-expandable nodes, this is `null`.
|
|
1169
|
+
*/
|
|
1170
|
+
_getAriaExpanded() {
|
|
1171
|
+
if (!this._isExpandable()) {
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
return String(this.isExpanded);
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Determines the size of this node's parent's child set.
|
|
1178
|
+
*
|
|
1179
|
+
* This is intended to be used for `aria-setsize`.
|
|
1180
|
+
*/
|
|
1181
|
+
_getSetSize() {
|
|
1182
|
+
return this._tree._getSetSize(this._data);
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Determines the index (starting from 1) of this node in its parent's child set.
|
|
1186
|
+
*
|
|
1187
|
+
* This is intended to be used for `aria-posinset`.
|
|
1188
|
+
*/
|
|
1189
|
+
_getPositionInSet() {
|
|
1190
|
+
return this._tree._getPositionInSet(this._data);
|
|
503
1191
|
}
|
|
504
1192
|
constructor(_elementRef, _tree) {
|
|
505
1193
|
this._elementRef = _elementRef;
|
|
506
1194
|
this._tree = _tree;
|
|
1195
|
+
this._tabindex = -1;
|
|
1196
|
+
/** This emits when the node has been programatically activated or activated by keyboard. */
|
|
1197
|
+
this.activation = new EventEmitter();
|
|
1198
|
+
/** This emits when the node's expansion status has been changed. */
|
|
1199
|
+
this.expandedChange = new EventEmitter();
|
|
507
1200
|
/** Subject that emits when the component has been destroyed. */
|
|
508
1201
|
this._destroyed = new Subject();
|
|
509
1202
|
/** Emits when the node's data has changed. */
|
|
510
1203
|
this._dataChanges = new Subject();
|
|
1204
|
+
this._inputIsExpandable = false;
|
|
1205
|
+
/**
|
|
1206
|
+
* Flag used to determine whether or not we should be focusing the actual element based on
|
|
1207
|
+
* some user interaction (click or focus). On click, we don't forcibly focus the element
|
|
1208
|
+
* since the click could trigger some other component that wants to grab its own focus
|
|
1209
|
+
* (e.g. menu, dialog).
|
|
1210
|
+
*/
|
|
1211
|
+
this._shouldFocus = true;
|
|
511
1212
|
this._changeDetectorRef = inject(ChangeDetectorRef);
|
|
512
1213
|
CdkTreeNode.mostRecentTreeNode = this;
|
|
513
|
-
this.role = 'treeitem';
|
|
514
1214
|
}
|
|
515
1215
|
ngOnInit() {
|
|
516
1216
|
this._parentNodeAriaLevel = getParentNodeAriaLevel(this._elementRef.nativeElement);
|
|
517
|
-
this.
|
|
518
|
-
|
|
519
|
-
.pipe(map(() => this.isExpanded), distinctUntilChanged())
|
|
1217
|
+
this._tree
|
|
1218
|
+
._getExpansionModel()
|
|
1219
|
+
.changed.pipe(map(() => this.isExpanded), distinctUntilChanged())
|
|
520
1220
|
.subscribe(() => {
|
|
521
1221
|
this._changeDetectorRef.markForCheck();
|
|
522
1222
|
});
|
|
1223
|
+
this._tree._setNodeTypeIfUnset('flat');
|
|
1224
|
+
this._tree._registerNode(this);
|
|
523
1225
|
}
|
|
524
1226
|
ngOnDestroy() {
|
|
525
1227
|
// If this is the last tree node being destroyed,
|
|
@@ -531,21 +1233,63 @@ class CdkTreeNode {
|
|
|
531
1233
|
this._destroyed.next();
|
|
532
1234
|
this._destroyed.complete();
|
|
533
1235
|
}
|
|
534
|
-
|
|
1236
|
+
getParent() {
|
|
1237
|
+
return this._tree._getNodeParent(this) ?? null;
|
|
1238
|
+
}
|
|
1239
|
+
getChildren() {
|
|
1240
|
+
return this._tree._getNodeChildren(this);
|
|
1241
|
+
}
|
|
1242
|
+
/** Focuses this data node. Implemented for TreeKeyManagerItem. */
|
|
535
1243
|
focus() {
|
|
536
|
-
this.
|
|
1244
|
+
this._tabindex = 0;
|
|
1245
|
+
if (this._shouldFocus) {
|
|
1246
|
+
this._elementRef.nativeElement.focus();
|
|
1247
|
+
}
|
|
1248
|
+
this._changeDetectorRef.markForCheck();
|
|
537
1249
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
1250
|
+
/** Defocus this data node. */
|
|
1251
|
+
unfocus() {
|
|
1252
|
+
this._tabindex = -1;
|
|
1253
|
+
this._changeDetectorRef.markForCheck();
|
|
1254
|
+
}
|
|
1255
|
+
/** Emits an activation event. Implemented for TreeKeyManagerItem. */
|
|
1256
|
+
activate() {
|
|
1257
|
+
if (this.isDisabled) {
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
this.activation.next(this._data);
|
|
1261
|
+
}
|
|
1262
|
+
/** Collapses this data node. Implemented for TreeKeyManagerItem. */
|
|
1263
|
+
collapse() {
|
|
1264
|
+
if (this.isExpandable) {
|
|
1265
|
+
this._tree.collapse(this._data);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
/** Expands this data node. Implemented for TreeKeyManagerItem. */
|
|
1269
|
+
expand() {
|
|
1270
|
+
if (this.isExpandable) {
|
|
1271
|
+
this._tree.expand(this._data);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
_focusItem() {
|
|
1275
|
+
if (this.isDisabled) {
|
|
1276
|
+
return;
|
|
544
1277
|
}
|
|
545
|
-
this.
|
|
1278
|
+
this._tree._keyManager.focusItem(this);
|
|
1279
|
+
}
|
|
1280
|
+
_setActiveItem() {
|
|
1281
|
+
if (this.isDisabled) {
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
this._shouldFocus = false;
|
|
1285
|
+
this._tree._keyManager.focusItem(this);
|
|
1286
|
+
this._shouldFocus = true;
|
|
1287
|
+
}
|
|
1288
|
+
_emitExpansionState(expanded) {
|
|
1289
|
+
this.expandedChange.emit(expanded);
|
|
546
1290
|
}
|
|
547
1291
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNode, deps: [{ token: i0.ElementRef }, { token: CdkTree }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
548
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
1292
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "18.1.0", type: CdkTreeNode, isStandalone: true, selector: "cdk-tree-node", inputs: { role: "role", isExpandable: ["isExpandable", "isExpandable", booleanAttribute], isExpanded: "isExpanded", isDisabled: ["isDisabled", "isDisabled", booleanAttribute], typeaheadLabel: ["cdkTreeNodeTypeaheadLabel", "typeaheadLabel"] }, outputs: { activation: "activation", expandedChange: "expandedChange" }, host: { attributes: { "role": "treeitem" }, listeners: { "click": "_setActiveItem()", "focus": "_focusItem()" }, properties: { "attr.aria-expanded": "_getAriaExpanded()", "attr.aria-level": "level + 1", "attr.aria-posinset": "_getPositionInSet()", "attr.aria-setsize": "_getSetSize()", "tabindex": "_tabindex" }, classAttribute: "cdk-tree-node" }, exportAs: ["cdkTreeNode"], ngImport: i0 }); }
|
|
549
1293
|
}
|
|
550
1294
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNode, decorators: [{
|
|
551
1295
|
type: Directive,
|
|
@@ -554,12 +1298,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
|
|
|
554
1298
|
exportAs: 'cdkTreeNode',
|
|
555
1299
|
host: {
|
|
556
1300
|
'class': 'cdk-tree-node',
|
|
557
|
-
'[attr.aria-expanded]': '
|
|
1301
|
+
'[attr.aria-expanded]': '_getAriaExpanded()',
|
|
1302
|
+
'[attr.aria-level]': 'level + 1',
|
|
1303
|
+
'[attr.aria-posinset]': '_getPositionInSet()',
|
|
1304
|
+
'[attr.aria-setsize]': '_getSetSize()',
|
|
1305
|
+
'[tabindex]': '_tabindex',
|
|
1306
|
+
'role': 'treeitem',
|
|
1307
|
+
'(click)': '_setActiveItem()',
|
|
1308
|
+
'(focus)': '_focusItem()',
|
|
558
1309
|
},
|
|
559
1310
|
standalone: true,
|
|
560
1311
|
}]
|
|
561
1312
|
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: CdkTree }], propDecorators: { role: [{
|
|
562
1313
|
type: Input
|
|
1314
|
+
}], isExpandable: [{
|
|
1315
|
+
type: Input,
|
|
1316
|
+
args: [{ transform: booleanAttribute }]
|
|
1317
|
+
}], isExpanded: [{
|
|
1318
|
+
type: Input
|
|
1319
|
+
}], isDisabled: [{
|
|
1320
|
+
type: Input,
|
|
1321
|
+
args: [{ transform: booleanAttribute }]
|
|
1322
|
+
}], typeaheadLabel: [{
|
|
1323
|
+
type: Input,
|
|
1324
|
+
args: ['cdkTreeNodeTypeaheadLabel']
|
|
1325
|
+
}], activation: [{
|
|
1326
|
+
type: Output
|
|
1327
|
+
}], expandedChange: [{
|
|
1328
|
+
type: Output
|
|
563
1329
|
}] } });
|
|
564
1330
|
function getParentNodeAriaLevel(nodeElement) {
|
|
565
1331
|
let parent = nodeElement.parentElement;
|
|
@@ -600,18 +1366,10 @@ class CdkNestedTreeNode extends CdkTreeNode {
|
|
|
600
1366
|
}
|
|
601
1367
|
ngAfterContentInit() {
|
|
602
1368
|
this._dataDiffer = this._differs.find([]).create(this._tree.trackBy);
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (Array.isArray(childrenNodes)) {
|
|
608
|
-
this.updateChildrenNodes(childrenNodes);
|
|
609
|
-
}
|
|
610
|
-
else if (isObservable(childrenNodes)) {
|
|
611
|
-
childrenNodes
|
|
612
|
-
.pipe(takeUntil(this._destroyed))
|
|
613
|
-
.subscribe(result => this.updateChildrenNodes(result));
|
|
614
|
-
}
|
|
1369
|
+
this._tree
|
|
1370
|
+
._getDirectChildren(this.data)
|
|
1371
|
+
.pipe(takeUntil(this._destroyed))
|
|
1372
|
+
.subscribe(result => this.updateChildrenNodes(result));
|
|
615
1373
|
this.nodeOutlet.changes
|
|
616
1374
|
.pipe(takeUntil(this._destroyed))
|
|
617
1375
|
.subscribe(() => this.updateChildrenNodes());
|
|
@@ -619,6 +1377,7 @@ class CdkNestedTreeNode extends CdkTreeNode {
|
|
|
619
1377
|
// This is a workaround for https://github.com/angular/angular/issues/23091
|
|
620
1378
|
// In aot mode, the lifecycle hooks from parent class are not called.
|
|
621
1379
|
ngOnInit() {
|
|
1380
|
+
this._tree._setNodeTypeIfUnset('nested');
|
|
622
1381
|
super.ngOnInit();
|
|
623
1382
|
}
|
|
624
1383
|
ngOnDestroy() {
|
|
@@ -733,9 +1492,7 @@ class CdkTreeNodePadding {
|
|
|
733
1492
|
}
|
|
734
1493
|
/** The padding indent value for the tree node. Returns a string with px numbers if not null. */
|
|
735
1494
|
_paddingIndent() {
|
|
736
|
-
const nodeLevel = this._treeNode.data && this._tree.
|
|
737
|
-
? this._tree.treeControl.getLevel(this._treeNode.data)
|
|
738
|
-
: null;
|
|
1495
|
+
const nodeLevel = (this._treeNode.data && this._tree._getLevel(this._treeNode.data)) ?? null;
|
|
739
1496
|
const level = this._level == null ? nodeLevel : this._level;
|
|
740
1497
|
return typeof level === 'number' ? `${level * this._indent}${this.indentUnits}` : null;
|
|
741
1498
|
}
|
|
@@ -781,7 +1538,7 @@ class CdkTreeNodePadding {
|
|
|
781
1538
|
this._indent = numberAttribute(value);
|
|
782
1539
|
this._setPadding();
|
|
783
1540
|
}
|
|
784
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodePadding, deps: [{ token: CdkTreeNode }, { token: CdkTree }, { token: i0.ElementRef }, { token:
|
|
1541
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodePadding, deps: [{ token: CdkTreeNode }, { token: CdkTree }, { token: i0.ElementRef }, { token: i1.Directionality, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
785
1542
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "18.1.0", type: CdkTreeNodePadding, isStandalone: true, selector: "[cdkTreeNodePadding]", inputs: { level: ["cdkTreeNodePadding", "level", numberAttribute], indent: ["cdkTreeNodePaddingIndent", "indent"] }, ngImport: i0 }); }
|
|
786
1543
|
}
|
|
787
1544
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodePadding, decorators: [{
|
|
@@ -790,7 +1547,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
|
|
|
790
1547
|
selector: '[cdkTreeNodePadding]',
|
|
791
1548
|
standalone: true,
|
|
792
1549
|
}]
|
|
793
|
-
}], ctorParameters: () => [{ type: CdkTreeNode }, { type: CdkTree }, { type: i0.ElementRef }, { type:
|
|
1550
|
+
}], ctorParameters: () => [{ type: CdkTreeNode }, { type: CdkTree }, { type: i0.ElementRef }, { type: i1.Directionality, decorators: [{
|
|
794
1551
|
type: Optional
|
|
795
1552
|
}] }], propDecorators: { level: [{
|
|
796
1553
|
type: Input,
|
|
@@ -801,7 +1558,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
|
|
|
801
1558
|
}] } });
|
|
802
1559
|
|
|
803
1560
|
/**
|
|
804
|
-
* Node toggle to expand
|
|
1561
|
+
* Node toggle to expand and collapse the node.
|
|
805
1562
|
*/
|
|
806
1563
|
class CdkTreeNodeToggle {
|
|
807
1564
|
constructor(_tree, _treeNode) {
|
|
@@ -810,21 +1567,28 @@ class CdkTreeNodeToggle {
|
|
|
810
1567
|
/** Whether expand/collapse the node recursively. */
|
|
811
1568
|
this.recursive = false;
|
|
812
1569
|
}
|
|
813
|
-
|
|
1570
|
+
// Toggle the expanded or collapsed state of this node.
|
|
1571
|
+
//
|
|
1572
|
+
// Focus this node with expanding or collapsing it. This ensures that the active node will always
|
|
1573
|
+
// be visible when expanding and collapsing.
|
|
1574
|
+
_toggle() {
|
|
814
1575
|
this.recursive
|
|
815
|
-
? this._tree.
|
|
816
|
-
: this._tree.
|
|
817
|
-
|
|
1576
|
+
? this._tree.toggleDescendants(this._treeNode.data)
|
|
1577
|
+
: this._tree.toggle(this._treeNode.data);
|
|
1578
|
+
this._tree._keyManager.focusItem(this._treeNode);
|
|
818
1579
|
}
|
|
819
1580
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodeToggle, deps: [{ token: CdkTree }, { token: CdkTreeNode }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
820
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "18.1.0", type: CdkTreeNodeToggle, isStandalone: true, selector: "[cdkTreeNodeToggle]", inputs: { recursive: ["cdkTreeNodeToggleRecursive", "recursive", booleanAttribute] }, host: { listeners: { "click": "_toggle($event)" } }, ngImport: i0 }); }
|
|
1581
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "18.1.0", type: CdkTreeNodeToggle, isStandalone: true, selector: "[cdkTreeNodeToggle]", inputs: { recursive: ["cdkTreeNodeToggleRecursive", "recursive", booleanAttribute] }, host: { attributes: { "tabindex": "-1" }, listeners: { "click": "_toggle(); $event.stopPropagation();", "keydown.Enter": "_toggle(); $event.preventDefault();", "keydown.Space": "_toggle(); $event.preventDefault();" } }, ngImport: i0 }); }
|
|
821
1582
|
}
|
|
822
1583
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodeToggle, decorators: [{
|
|
823
1584
|
type: Directive,
|
|
824
1585
|
args: [{
|
|
825
1586
|
selector: '[cdkTreeNodeToggle]',
|
|
826
1587
|
host: {
|
|
827
|
-
'(click)': '_toggle($event)',
|
|
1588
|
+
'(click)': '_toggle(); $event.stopPropagation();',
|
|
1589
|
+
'(keydown.Enter)': '_toggle(); $event.preventDefault();',
|
|
1590
|
+
'(keydown.Space)': '_toggle(); $event.preventDefault();',
|
|
1591
|
+
'tabindex': '-1',
|
|
828
1592
|
},
|
|
829
1593
|
standalone: true,
|
|
830
1594
|
}]
|
|
@@ -871,5 +1635,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
|
|
|
871
1635
|
* Generated bundle index. Do not edit.
|
|
872
1636
|
*/
|
|
873
1637
|
|
|
874
|
-
export { BaseTreeControl, CDK_TREE_NODE_OUTLET_NODE, CdkNestedTreeNode, CdkTree, CdkTreeModule, CdkTreeNode, CdkTreeNodeDef, CdkTreeNodeOutlet, CdkTreeNodeOutletContext, CdkTreeNodePadding, CdkTreeNodeToggle, FlatTreeControl, NestedTreeControl,
|
|
1638
|
+
export { BaseTreeControl, CDK_TREE_NODE_OUTLET_NODE, CdkNestedTreeNode, CdkTree, CdkTreeModule, CdkTreeNode, CdkTreeNodeDef, CdkTreeNodeOutlet, CdkTreeNodeOutletContext, CdkTreeNodePadding, CdkTreeNodeToggle, FlatTreeControl, NestedTreeControl, getMultipleTreeControlsError, getTreeControlMissingError, getTreeMissingMatchingNodeDefError, getTreeMultipleDefaultNodeDefsError, getTreeNoValidDataSourceError };
|
|
875
1639
|
//# sourceMappingURL=tree.mjs.map
|