@angular/cdk 18.2.0-next.0 → 18.2.0-next.2
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/esm2022/a11y/a11y-module.mjs +5 -5
- package/esm2022/a11y/aria-describer/aria-describer.mjs +4 -4
- package/esm2022/a11y/focus-monitor/focus-monitor.mjs +7 -7
- package/esm2022/a11y/focus-trap/configurable-focus-trap-factory.mjs +4 -4
- package/esm2022/a11y/focus-trap/focus-trap-manager.mjs +4 -4
- package/esm2022/a11y/focus-trap/focus-trap.mjs +7 -7
- package/esm2022/a11y/high-contrast-mode/high-contrast-mode-detector.mjs +4 -4
- package/esm2022/a11y/input-modality/input-modality-detector.mjs +4 -4
- package/esm2022/a11y/interactivity-checker/interactivity-checker.mjs +4 -4
- 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/live-announcer/live-announcer.mjs +7 -7
- package/esm2022/a11y/public-api.mjs +4 -1
- package/esm2022/accordion/accordion-item.mjs +4 -4
- package/esm2022/accordion/accordion-module.mjs +5 -5
- package/esm2022/accordion/accordion.mjs +4 -4
- package/esm2022/bidi/bidi-module.mjs +5 -5
- package/esm2022/bidi/dir.mjs +4 -4
- package/esm2022/bidi/directionality.mjs +4 -4
- package/esm2022/clipboard/clipboard-module.mjs +5 -5
- package/esm2022/clipboard/clipboard.mjs +4 -4
- package/esm2022/clipboard/copy-to-clipboard.mjs +4 -4
- 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/collections/unique-selection-dispatcher.mjs +4 -4
- package/esm2022/dialog/dialog-container.mjs +4 -4
- package/esm2022/dialog/dialog-module.mjs +5 -5
- package/esm2022/dialog/dialog.mjs +4 -4
- package/esm2022/drag-drop/directives/drag-handle.mjs +4 -4
- package/esm2022/drag-drop/directives/drag-placeholder.mjs +4 -4
- package/esm2022/drag-drop/directives/drag-preview.mjs +4 -4
- package/esm2022/drag-drop/directives/drag.mjs +4 -4
- package/esm2022/drag-drop/directives/drop-list-group.mjs +4 -4
- package/esm2022/drag-drop/directives/drop-list.mjs +4 -4
- package/esm2022/drag-drop/drag-drop-module.mjs +5 -5
- package/esm2022/drag-drop/drag-drop-registry.mjs +7 -7
- package/esm2022/drag-drop/drag-drop.mjs +4 -4
- package/esm2022/drag-drop/drag-ref.mjs +4 -2
- package/esm2022/drag-drop/preview-ref.mjs +4 -1
- package/esm2022/layout/breakpoints-observer.mjs +4 -4
- package/esm2022/layout/layout-module.mjs +5 -5
- package/esm2022/layout/media-matcher.mjs +4 -4
- package/esm2022/listbox/listbox-module.mjs +5 -5
- package/esm2022/listbox/listbox.mjs +7 -7
- package/esm2022/menu/context-menu-trigger.mjs +7 -7
- package/esm2022/menu/menu-aim.mjs +7 -7
- package/esm2022/menu/menu-bar.mjs +4 -4
- package/esm2022/menu/menu-base.mjs +4 -4
- package/esm2022/menu/menu-group.mjs +4 -4
- package/esm2022/menu/menu-item-checkbox.mjs +4 -4
- package/esm2022/menu/menu-item-radio.mjs +4 -4
- package/esm2022/menu/menu-item-selectable.mjs +4 -4
- package/esm2022/menu/menu-item.mjs +4 -4
- package/esm2022/menu/menu-module.mjs +5 -5
- package/esm2022/menu/menu-stack.mjs +4 -4
- package/esm2022/menu/menu-trigger-base.mjs +4 -4
- package/esm2022/menu/menu-trigger.mjs +4 -4
- package/esm2022/menu/menu.mjs +4 -4
- package/esm2022/observers/observe-content.mjs +14 -14
- package/esm2022/observers/private/shared-resize-observer.mjs +4 -4
- package/esm2022/overlay/dispatchers/base-overlay-dispatcher.mjs +4 -4
- package/esm2022/overlay/dispatchers/overlay-keyboard-dispatcher.mjs +4 -4
- package/esm2022/overlay/dispatchers/overlay-outside-click-dispatcher.mjs +4 -4
- package/esm2022/overlay/fullscreen-overlay-container.mjs +4 -4
- package/esm2022/overlay/overlay-container.mjs +4 -4
- package/esm2022/overlay/overlay-directives.mjs +7 -7
- package/esm2022/overlay/overlay-module.mjs +5 -5
- package/esm2022/overlay/overlay.mjs +4 -4
- package/esm2022/overlay/position/overlay-position-builder.mjs +4 -4
- package/esm2022/overlay/scroll/scroll-strategy-options.mjs +4 -4
- package/esm2022/platform/platform-module.mjs +5 -5
- package/esm2022/platform/platform.mjs +4 -4
- package/esm2022/portal/portal-directives.mjs +17 -17
- package/esm2022/scrolling/fixed-size-virtual-scroll.mjs +4 -4
- package/esm2022/scrolling/scroll-dispatcher.mjs +4 -4
- package/esm2022/scrolling/scrollable.mjs +4 -4
- package/esm2022/scrolling/scrolling-module.mjs +9 -9
- package/esm2022/scrolling/viewport-ruler.mjs +4 -4
- package/esm2022/scrolling/virtual-for-of.mjs +4 -4
- package/esm2022/scrolling/virtual-scroll-viewport.mjs +4 -4
- package/esm2022/scrolling/virtual-scrollable-element.mjs +4 -4
- package/esm2022/scrolling/virtual-scrollable-window.mjs +4 -4
- package/esm2022/scrolling/virtual-scrollable.mjs +4 -4
- package/esm2022/stepper/step-header.mjs +4 -4
- package/esm2022/stepper/step-label.mjs +4 -4
- package/esm2022/stepper/stepper-button.mjs +7 -7
- package/esm2022/stepper/stepper-module.mjs +5 -5
- package/esm2022/stepper/stepper.mjs +7 -7
- package/esm2022/table/cell.mjs +22 -22
- package/esm2022/table/coalesced-style-scheduler.mjs +4 -4
- package/esm2022/table/row.mjs +28 -28
- package/esm2022/table/table-module.mjs +5 -5
- package/esm2022/table/table.mjs +19 -19
- package/esm2022/table/text-column.mjs +4 -4
- package/esm2022/text-field/autofill.mjs +7 -7
- package/esm2022/text-field/autosize.mjs +4 -4
- package/esm2022/text-field/text-field-module.mjs +5 -5
- 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 +9 -18
- package/esm2022/tree/node.mjs +4 -4
- package/esm2022/tree/outlet.mjs +4 -4
- package/esm2022/tree/padding.mjs +5 -7
- package/esm2022/tree/toggle.mjs +17 -10
- package/esm2022/tree/tree-errors.mjs +7 -6
- package/esm2022/tree/tree-module.mjs +5 -5
- package/esm2022/tree/tree.mjs +810 -70
- package/esm2022/version.mjs +1 -1
- package/fesm2022/a11y.mjs +560 -80
- package/fesm2022/a11y.mjs.map +1 -1
- package/fesm2022/accordion.mjs +10 -10
- package/fesm2022/accordion.mjs.map +1 -1
- package/fesm2022/bidi.mjs +10 -10
- package/fesm2022/bidi.mjs.map +1 -1
- package/fesm2022/cdk.mjs +1 -1
- package/fesm2022/cdk.mjs.map +1 -1
- package/fesm2022/clipboard.mjs +10 -10
- package/fesm2022/clipboard.mjs.map +1 -1
- package/fesm2022/coercion/private.mjs +19 -0
- package/fesm2022/coercion/private.mjs.map +1 -0
- package/fesm2022/collections.mjs +3 -3
- package/fesm2022/collections.mjs.map +1 -1
- package/fesm2022/dialog.mjs +10 -10
- package/fesm2022/dialog.mjs.map +1 -1
- package/fesm2022/drag-drop.mjs +37 -32
- package/fesm2022/drag-drop.mjs.map +1 -1
- package/fesm2022/layout.mjs +10 -10
- package/fesm2022/layout.mjs.map +1 -1
- package/fesm2022/listbox.mjs +10 -10
- package/fesm2022/listbox.mjs.map +1 -1
- package/fesm2022/menu.mjs +49 -49
- package/fesm2022/menu.mjs.map +1 -1
- package/fesm2022/observers/private.mjs +3 -3
- package/fesm2022/observers/private.mjs.map +1 -1
- package/fesm2022/observers.mjs +13 -13
- package/fesm2022/observers.mjs.map +1 -1
- package/fesm2022/overlay.mjs +34 -34
- package/fesm2022/overlay.mjs.map +1 -1
- package/fesm2022/platform.mjs +7 -7
- package/fesm2022/platform.mjs.map +1 -1
- package/fesm2022/portal.mjs +16 -16
- package/fesm2022/portal.mjs.map +1 -1
- package/fesm2022/scrolling.mjs +35 -35
- package/fesm2022/scrolling.mjs.map +1 -1
- package/fesm2022/stepper.mjs +22 -22
- package/fesm2022/stepper.mjs.map +1 -1
- package/fesm2022/table.mjs +76 -76
- package/fesm2022/table.mjs.map +1 -1
- package/fesm2022/text-field.mjs +13 -13
- package/fesm2022/text-field.mjs.map +1 -1
- package/fesm2022/tree.mjs +868 -118
- package/fesm2022/tree.mjs.map +1 -1
- package/package.json +7 -1
- package/schematics/ng-add/index.js +1 -1
- package/schematics/ng-add/index.mjs +1 -1
- package/tree/index.d.ts +303 -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.
|
|
@@ -160,10 +182,10 @@ class CdkTreeNodeOutlet {
|
|
|
160
182
|
this.viewContainer = viewContainer;
|
|
161
183
|
this._node = _node;
|
|
162
184
|
}
|
|
163
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0
|
|
164
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.0
|
|
185
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodeOutlet, deps: [{ token: i0.ViewContainerRef }, { token: CDK_TREE_NODE_OUTLET_NODE, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
186
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.0", type: CdkTreeNodeOutlet, isStandalone: true, selector: "[cdkTreeNodeOutlet]", ngImport: i0 }); }
|
|
165
187
|
}
|
|
166
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0
|
|
188
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodeOutlet, decorators: [{
|
|
167
189
|
type: Directive,
|
|
168
190
|
args: [{
|
|
169
191
|
selector: '[cdkTreeNodeOutlet]',
|
|
@@ -191,10 +213,10 @@ class CdkTreeNodeDef {
|
|
|
191
213
|
constructor(template) {
|
|
192
214
|
this.template = template;
|
|
193
215
|
}
|
|
194
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0
|
|
195
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.0
|
|
216
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodeDef, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
217
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.0", type: CdkTreeNodeDef, isStandalone: true, selector: "[cdkTreeNodeDef]", inputs: { when: ["cdkTreeNodeDefWhen", "when"] }, ngImport: i0 }); }
|
|
196
218
|
}
|
|
197
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0
|
|
219
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodeDef, decorators: [{
|
|
198
220
|
type: Directive,
|
|
199
221
|
args: [{
|
|
200
222
|
selector: '[cdkTreeNodeDef]',
|
|
@@ -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,10 +607,429 @@ 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
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0
|
|
1032
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTree, decorators: [{
|
|
425
1033
|
type: Component,
|
|
426
1034
|
args: [{
|
|
427
1035
|
selector: 'cdk-tree',
|
|
@@ -430,6 +1038,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0-next.3",
|
|
|
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-next.3",
|
|
|
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-next.3",
|
|
|
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,54 +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
|
-
get isExpanded() {
|
|
494
|
-
return this._tree.treeControl.isExpanded(this._data);
|
|
495
|
-
}
|
|
496
1133
|
/* If leaf node, return true to not assign aria-expanded attribute */
|
|
497
1134
|
get isLeafNode() {
|
|
498
1135
|
// If flat tree node data returns false for expandable property, it's a leaf node
|
|
499
|
-
if (this._tree.treeControl
|
|
1136
|
+
if (this._tree.treeControl?.isExpandable !== undefined &&
|
|
500
1137
|
!this._tree.treeControl.isExpandable(this._data)) {
|
|
501
1138
|
return true;
|
|
502
1139
|
// If nested tree node data returns 0 descendants, it's a leaf node
|
|
503
1140
|
}
|
|
504
|
-
else if (this._tree.treeControl
|
|
505
|
-
this._tree.treeControl
|
|
1141
|
+
else if (this._tree.treeControl?.isExpandable === undefined &&
|
|
1142
|
+
this._tree.treeControl?.getDescendants(this._data).length === 0) {
|
|
506
1143
|
return true;
|
|
507
1144
|
}
|
|
508
1145
|
return false;
|
|
509
1146
|
}
|
|
510
1147
|
get level() {
|
|
511
|
-
// If the
|
|
1148
|
+
// If the tree has a levelAccessor, use it to get the level. Otherwise read the
|
|
512
1149
|
// aria-level off the parent node and use it as the level for this node (note aria-level is
|
|
513
1150
|
// 1-indexed, while this property is 0-indexed, so we don't need to increment).
|
|
514
|
-
return this._tree.
|
|
515
|
-
|
|
516
|
-
|
|
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);
|
|
517
1191
|
}
|
|
518
1192
|
constructor(_elementRef, _tree) {
|
|
519
1193
|
this._elementRef = _elementRef;
|
|
520
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();
|
|
521
1200
|
/** Subject that emits when the component has been destroyed. */
|
|
522
1201
|
this._destroyed = new Subject();
|
|
523
1202
|
/** Emits when the node's data has changed. */
|
|
524
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;
|
|
525
1212
|
this._changeDetectorRef = inject(ChangeDetectorRef);
|
|
526
1213
|
CdkTreeNode.mostRecentTreeNode = this;
|
|
527
|
-
this.role = 'treeitem';
|
|
528
1214
|
}
|
|
529
1215
|
ngOnInit() {
|
|
530
1216
|
this._parentNodeAriaLevel = getParentNodeAriaLevel(this._elementRef.nativeElement);
|
|
531
|
-
this.
|
|
532
|
-
|
|
533
|
-
.pipe(map(() => this.isExpanded), distinctUntilChanged())
|
|
1217
|
+
this._tree
|
|
1218
|
+
._getExpansionModel()
|
|
1219
|
+
.changed.pipe(map(() => this.isExpanded), distinctUntilChanged())
|
|
534
1220
|
.subscribe(() => {
|
|
535
1221
|
this._changeDetectorRef.markForCheck();
|
|
536
1222
|
});
|
|
1223
|
+
this._tree._setNodeTypeIfUnset('flat');
|
|
1224
|
+
this._tree._registerNode(this);
|
|
537
1225
|
}
|
|
538
1226
|
ngOnDestroy() {
|
|
539
1227
|
// If this is the last tree node being destroyed,
|
|
@@ -545,35 +1233,99 @@ class CdkTreeNode {
|
|
|
545
1233
|
this._destroyed.next();
|
|
546
1234
|
this._destroyed.complete();
|
|
547
1235
|
}
|
|
548
|
-
|
|
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. */
|
|
549
1243
|
focus() {
|
|
550
|
-
this.
|
|
1244
|
+
this._tabindex = 0;
|
|
1245
|
+
if (this._shouldFocus) {
|
|
1246
|
+
this._elementRef.nativeElement.focus();
|
|
1247
|
+
}
|
|
1248
|
+
this._changeDetectorRef.markForCheck();
|
|
551
1249
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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;
|
|
558
1277
|
}
|
|
559
|
-
this.
|
|
1278
|
+
this._tree._keyManager.focusItem(this);
|
|
560
1279
|
}
|
|
561
|
-
|
|
562
|
-
|
|
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);
|
|
1290
|
+
}
|
|
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 }); }
|
|
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 }); }
|
|
563
1293
|
}
|
|
564
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0
|
|
1294
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNode, decorators: [{
|
|
565
1295
|
type: Directive,
|
|
566
1296
|
args: [{
|
|
567
1297
|
selector: 'cdk-tree-node',
|
|
568
1298
|
exportAs: 'cdkTreeNode',
|
|
569
1299
|
host: {
|
|
570
1300
|
'class': 'cdk-tree-node',
|
|
571
|
-
'[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()',
|
|
572
1309
|
},
|
|
573
1310
|
standalone: true,
|
|
574
1311
|
}]
|
|
575
1312
|
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: CdkTree }], propDecorators: { role: [{
|
|
576
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
|
|
577
1329
|
}] } });
|
|
578
1330
|
function getParentNodeAriaLevel(nodeElement) {
|
|
579
1331
|
let parent = nodeElement.parentElement;
|
|
@@ -614,18 +1366,10 @@ class CdkNestedTreeNode extends CdkTreeNode {
|
|
|
614
1366
|
}
|
|
615
1367
|
ngAfterContentInit() {
|
|
616
1368
|
this._dataDiffer = this._differs.find([]).create(this._tree.trackBy);
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (Array.isArray(childrenNodes)) {
|
|
622
|
-
this.updateChildrenNodes(childrenNodes);
|
|
623
|
-
}
|
|
624
|
-
else if (isObservable(childrenNodes)) {
|
|
625
|
-
childrenNodes
|
|
626
|
-
.pipe(takeUntil(this._destroyed))
|
|
627
|
-
.subscribe(result => this.updateChildrenNodes(result));
|
|
628
|
-
}
|
|
1369
|
+
this._tree
|
|
1370
|
+
._getDirectChildren(this.data)
|
|
1371
|
+
.pipe(takeUntil(this._destroyed))
|
|
1372
|
+
.subscribe(result => this.updateChildrenNodes(result));
|
|
629
1373
|
this.nodeOutlet.changes
|
|
630
1374
|
.pipe(takeUntil(this._destroyed))
|
|
631
1375
|
.subscribe(() => this.updateChildrenNodes());
|
|
@@ -633,6 +1377,7 @@ class CdkNestedTreeNode extends CdkTreeNode {
|
|
|
633
1377
|
// This is a workaround for https://github.com/angular/angular/issues/23091
|
|
634
1378
|
// In aot mode, the lifecycle hooks from parent class are not called.
|
|
635
1379
|
ngOnInit() {
|
|
1380
|
+
this._tree._setNodeTypeIfUnset('nested');
|
|
636
1381
|
super.ngOnInit();
|
|
637
1382
|
}
|
|
638
1383
|
ngOnDestroy() {
|
|
@@ -669,13 +1414,13 @@ class CdkNestedTreeNode extends CdkTreeNode {
|
|
|
669
1414
|
// that we don't pick up the outlet of a child node by accident.
|
|
670
1415
|
return outlets && outlets.find(outlet => !outlet._node || outlet._node === this);
|
|
671
1416
|
}
|
|
672
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0
|
|
673
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.0
|
|
1417
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkNestedTreeNode, deps: [{ token: i0.ElementRef }, { token: CdkTree }, { token: i0.IterableDiffers }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1418
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.0", type: CdkNestedTreeNode, isStandalone: true, selector: "cdk-nested-tree-node", host: { classAttribute: "cdk-nested-tree-node" }, providers: [
|
|
674
1419
|
{ provide: CdkTreeNode, useExisting: CdkNestedTreeNode },
|
|
675
1420
|
{ provide: CDK_TREE_NODE_OUTLET_NODE, useExisting: CdkNestedTreeNode },
|
|
676
1421
|
], queries: [{ propertyName: "nodeOutlet", predicate: CdkTreeNodeOutlet, descendants: true }], exportAs: ["cdkNestedTreeNode"], usesInheritance: true, ngImport: i0 }); }
|
|
677
1422
|
}
|
|
678
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0
|
|
1423
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkNestedTreeNode, decorators: [{
|
|
679
1424
|
type: Directive,
|
|
680
1425
|
args: [{
|
|
681
1426
|
selector: 'cdk-nested-tree-node',
|
|
@@ -747,9 +1492,7 @@ class CdkTreeNodePadding {
|
|
|
747
1492
|
}
|
|
748
1493
|
/** The padding indent value for the tree node. Returns a string with px numbers if not null. */
|
|
749
1494
|
_paddingIndent() {
|
|
750
|
-
const nodeLevel = this._treeNode.data && this._tree.
|
|
751
|
-
? this._tree.treeControl.getLevel(this._treeNode.data)
|
|
752
|
-
: null;
|
|
1495
|
+
const nodeLevel = (this._treeNode.data && this._tree._getLevel(this._treeNode.data)) ?? null;
|
|
753
1496
|
const level = this._level == null ? nodeLevel : this._level;
|
|
754
1497
|
return typeof level === 'number' ? `${level * this._indent}${this.indentUnits}` : null;
|
|
755
1498
|
}
|
|
@@ -795,16 +1538,16 @@ class CdkTreeNodePadding {
|
|
|
795
1538
|
this._indent = numberAttribute(value);
|
|
796
1539
|
this._setPadding();
|
|
797
1540
|
}
|
|
798
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0
|
|
799
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "18.1.0
|
|
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 }); }
|
|
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 }); }
|
|
800
1543
|
}
|
|
801
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0
|
|
1544
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodePadding, decorators: [{
|
|
802
1545
|
type: Directive,
|
|
803
1546
|
args: [{
|
|
804
1547
|
selector: '[cdkTreeNodePadding]',
|
|
805
1548
|
standalone: true,
|
|
806
1549
|
}]
|
|
807
|
-
}], ctorParameters: () => [{ type: CdkTreeNode }, { type: CdkTree }, { type: i0.ElementRef }, { type:
|
|
1550
|
+
}], ctorParameters: () => [{ type: CdkTreeNode }, { type: CdkTree }, { type: i0.ElementRef }, { type: i1.Directionality, decorators: [{
|
|
808
1551
|
type: Optional
|
|
809
1552
|
}] }], propDecorators: { level: [{
|
|
810
1553
|
type: Input,
|
|
@@ -815,7 +1558,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0-next.3",
|
|
|
815
1558
|
}] } });
|
|
816
1559
|
|
|
817
1560
|
/**
|
|
818
|
-
* Node toggle to expand
|
|
1561
|
+
* Node toggle to expand and collapse the node.
|
|
819
1562
|
*/
|
|
820
1563
|
class CdkTreeNodeToggle {
|
|
821
1564
|
constructor(_tree, _treeNode) {
|
|
@@ -824,21 +1567,28 @@ class CdkTreeNodeToggle {
|
|
|
824
1567
|
/** Whether expand/collapse the node recursively. */
|
|
825
1568
|
this.recursive = false;
|
|
826
1569
|
}
|
|
827
|
-
|
|
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() {
|
|
828
1575
|
this.recursive
|
|
829
|
-
? this._tree.
|
|
830
|
-
: this._tree.
|
|
831
|
-
|
|
1576
|
+
? this._tree.toggleDescendants(this._treeNode.data)
|
|
1577
|
+
: this._tree.toggle(this._treeNode.data);
|
|
1578
|
+
this._tree._keyManager.focusItem(this._treeNode);
|
|
832
1579
|
}
|
|
833
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0
|
|
834
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "18.1.0
|
|
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 }); }
|
|
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 }); }
|
|
835
1582
|
}
|
|
836
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0
|
|
1583
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeNodeToggle, decorators: [{
|
|
837
1584
|
type: Directive,
|
|
838
1585
|
args: [{
|
|
839
1586
|
selector: '[cdkTreeNodeToggle]',
|
|
840
1587
|
host: {
|
|
841
|
-
'(click)': '_toggle($event)',
|
|
1588
|
+
'(click)': '_toggle(); $event.stopPropagation();',
|
|
1589
|
+
'(keydown.Enter)': '_toggle(); $event.preventDefault();',
|
|
1590
|
+
'(keydown.Space)': '_toggle(); $event.preventDefault();',
|
|
1591
|
+
'tabindex': '-1',
|
|
842
1592
|
},
|
|
843
1593
|
standalone: true,
|
|
844
1594
|
}]
|
|
@@ -857,8 +1607,8 @@ const EXPORTED_DECLARATIONS = [
|
|
|
857
1607
|
CdkTreeNodeOutlet,
|
|
858
1608
|
];
|
|
859
1609
|
class CdkTreeModule {
|
|
860
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0
|
|
861
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.1.0
|
|
1610
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1611
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeModule, imports: [CdkNestedTreeNode,
|
|
862
1612
|
CdkTreeNodeDef,
|
|
863
1613
|
CdkTreeNodePadding,
|
|
864
1614
|
CdkTreeNodeToggle,
|
|
@@ -871,9 +1621,9 @@ class CdkTreeModule {
|
|
|
871
1621
|
CdkTree,
|
|
872
1622
|
CdkTreeNode,
|
|
873
1623
|
CdkTreeNodeOutlet] }); }
|
|
874
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.1.0
|
|
1624
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeModule }); }
|
|
875
1625
|
}
|
|
876
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0
|
|
1626
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CdkTreeModule, decorators: [{
|
|
877
1627
|
type: NgModule,
|
|
878
1628
|
args: [{
|
|
879
1629
|
imports: EXPORTED_DECLARATIONS,
|
|
@@ -885,5 +1635,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0-next.3",
|
|
|
885
1635
|
* Generated bundle index. Do not edit.
|
|
886
1636
|
*/
|
|
887
1637
|
|
|
888
|
-
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 };
|
|
889
1639
|
//# sourceMappingURL=tree.mjs.map
|