Skip to content

Commit 7c03581

Browse files
committed
refactor(cdk/tree): address more PR comments; factor out helper methods
1 parent 23bed7d commit 7c03581

File tree

2 files changed

+93
-73
lines changed

2 files changed

+93
-73
lines changed

src/cdk/a11y/key-manager/tree-key-manager.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,16 @@ import {Typeahead} from './typeahead';
2424
* keyboard events occur.
2525
*/
2626
export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyManagerStrategy<T> {
27+
/** The index of the currently active (focused) item. */
2728
private _activeItemIndex = -1;
29+
/** The currently active (focused) item. */
2830
private _activeItem: T | null = null;
31+
/** Whether or not we activate the item when it's focused. */
2932
private _shouldActivationFollowFocus = false;
33+
/**
34+
* The orientation that the tree is laid out in. In `rtl` mode, the behavior of Left and
35+
* Right arrow are switched.
36+
*/
3037
private _horizontalOrientation: 'ltr' | 'rtl' = 'ltr';
3138

3239
// Keep tree items focusable when disabled. Align with
@@ -40,6 +47,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
4047
/** Function to determine equivalent items. */
4148
private _trackByFn: (item: T) => unknown = (item: T) => item;
4249

50+
/** Synchronous cache of the items to manage. */
4351
private _items: T[] = [];
4452

4553
private _typeahead?: Typeahead<T>;

src/cdk/tree/tree.ts

Lines changed: 85 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ type RenderingData<T> =
7575
| {
7676
flattenedNodes: null;
7777
nodeType: null;
78-
renderNodes: T[];
78+
renderNodes: readonly T[];
7979
}
8080
| {
81-
flattenedNodes: T[];
81+
flattenedNodes: readonly T[];
8282
nodeType: 'nested' | 'flat';
83-
renderNodes: [];
83+
renderNodes: readonly T[];
8484
};
8585

8686
/**
@@ -342,6 +342,14 @@ export class CdkTree<T, K = T>
342342
}
343343
}
344344

345+
private _getExpansionModel() {
346+
if (!this.treeControl) {
347+
this._expansionModel ??= new SelectionModel<K>(true);
348+
return this._expansionModel;
349+
}
350+
return this.treeControl.expansionModel;
351+
}
352+
345353
/** Set up a subscription for the data provided by the data source. */
346354
private _subscribeToDataChanges() {
347355
if (this._dataSubscription) {
@@ -365,15 +373,17 @@ export class CdkTree<T, K = T>
365373
return;
366374
}
367375

368-
let expansionModel;
369-
if (!this.treeControl) {
370-
this._expansionModel = new SelectionModel<K>(true);
371-
expansionModel = this._expansionModel;
372-
} else {
373-
expansionModel = this.treeControl.expansionModel;
374-
}
376+
this._dataSubscription = this._getRenderData(dataStream)
377+
.pipe(takeUntil(this._onDestroy))
378+
.subscribe(renderingData => {
379+
this._renderDataChanges(renderingData);
380+
});
381+
}
375382

376-
this._dataSubscription = combineLatest([
383+
/** Given an Observable containing a stream of the raw data, returns an Observable containing the RenderingData */
384+
private _getRenderData(dataStream: Observable<readonly T[]>): Observable<RenderingData<T>> {
385+
const expansionModel = this._getExpansionModel();
386+
return combineLatest([
377387
dataStream,
378388
this._nodeType,
379389
// We don't use the expansion data directly, however we add it here to essentially
@@ -384,24 +394,19 @@ export class CdkTree<T, K = T>
384394
this._emitExpansionChanges(expansionChanges);
385395
}),
386396
),
387-
])
388-
.pipe(
389-
switchMap(([data, nodeType]) => {
390-
if (nodeType === null) {
391-
return observableOf([{renderNodes: data}, nodeType] as const);
392-
}
397+
]).pipe(
398+
switchMap(([data, nodeType]) => {
399+
if (nodeType === null) {
400+
return observableOf({renderNodes: data, flattenedNodes: null, nodeType} as const);
401+
}
393402

394-
// If we're here, then we know what our node type is, and therefore can
395-
// perform our usual rendering pipeline, which necessitates converting the data
396-
return this._convertData(data, nodeType).pipe(
397-
map(convertedData => [convertedData, nodeType] as const),
398-
);
399-
}),
400-
takeUntil(this._onDestroy),
401-
)
402-
.subscribe(([data, nodeType]) => {
403-
this._renderDataChanges({nodeType, ...data} as RenderingData<T>);
404-
});
403+
// If we're here, then we know what our node type is, and therefore can
404+
// perform our usual rendering pipeline, which necessitates converting the data
405+
return this._computeRenderingData(data, nodeType).pipe(
406+
map(convertedData => ({...convertedData, nodeType}) as const),
407+
);
408+
}),
409+
);
405410
}
406411

407412
private _renderDataChanges(data: RenderingData<T>) {
@@ -586,10 +591,9 @@ export class CdkTree<T, K = T>
586591

587592
/** Whether the data node is expanded or collapsed. Returns true if it's expanded. */
588593
isExpanded(dataNode: T): boolean {
589-
return (
590-
this.treeControl?.isExpanded(dataNode) ??
591-
this._expansionModel?.isSelected(this._getExpansionKey(dataNode)) ??
592-
false
594+
return !!(
595+
this.treeControl?.isExpanded(dataNode) ||
596+
this._expansionModel?.isSelected(this._getExpansionKey(dataNode))
593597
);
594598
}
595599

@@ -733,26 +737,13 @@ export class CdkTree<T, K = T>
733737
if (!expanded) {
734738
return [];
735739
}
736-
const startIndex = flattenedNodes.findIndex(node => this._getExpansionKey(node) === key);
737-
const level = levelAccessor(dataNode) + 1;
738-
const results: T[] = [];
739-
740-
// Goes through flattened tree nodes in the `flattenedNodes` array, and get all direct
741-
// descendants. The level of descendants of a tree node must be equal to the level of the
742-
// given tree node + 1.
743-
// If we reach a node whose level is equal to the level of the tree node, we hit a sibling.
744-
// If we reach a node whose level is greater than the level of the tree node, we hit a
745-
// sibling of an ancestor.
746-
for (let i = startIndex + 1; i < flattenedNodes.length; i++) {
747-
const currentLevel = levelAccessor(flattenedNodes[i]);
748-
if (level > currentLevel) {
749-
break;
750-
}
751-
if (level === currentLevel) {
752-
results.push(flattenedNodes[i]);
753-
}
754-
}
755-
return results;
740+
return this._findChildrenByLevel(
741+
levelAccessor,
742+
flattenedNodes,
743+
744+
dataNode,
745+
1,
746+
);
756747
}),
757748
);
758749
}
@@ -763,6 +754,42 @@ export class CdkTree<T, K = T>
763754
throw getTreeControlMissingError();
764755
}
765756

757+
/**
758+
* Given the list of flattened nodes, the level accessor, and the level range within
759+
* which to consider children, finds the children for a given node.
760+
*
761+
* For example, for direct children, `levelDelta` would be 1. For all descendants,
762+
* `levelDelta` would be Infinity.
763+
*/
764+
private _findChildrenByLevel(
765+
levelAccessor: (node: T) => number,
766+
flattenedNodes: readonly T[],
767+
dataNode: T,
768+
levelDelta: number,
769+
): T[] {
770+
const key = this._getExpansionKey(dataNode);
771+
const startIndex = flattenedNodes.findIndex(node => this._getExpansionKey(node) === key);
772+
const dataNodeLevel = levelAccessor(dataNode);
773+
const expectedLevel = dataNodeLevel + levelDelta;
774+
const results: T[] = [];
775+
776+
// Goes through flattened tree nodes in the `flattenedNodes` array, and get all
777+
// descendants within a certain level range.
778+
//
779+
// If we reach a node whose level is equal to or less than the level of the tree node,
780+
// we hit a sibling or parent's sibling, and should stop.
781+
for (let i = startIndex + 1; i < flattenedNodes.length; i++) {
782+
const currentLevel = levelAccessor(flattenedNodes[i]);
783+
if (currentLevel <= dataNodeLevel) {
784+
break;
785+
}
786+
if (currentLevel <= expectedLevel) {
787+
results.push(flattenedNodes[i]);
788+
}
789+
}
790+
return results;
791+
}
792+
766793
/**
767794
* Adds the specified node component to the tree's internal registry.
768795
*
@@ -842,27 +869,12 @@ export class CdkTree<T, K = T>
842869
return observableOf(this.treeControl.getDescendants(dataNode));
843870
}
844871
if (this.levelAccessor) {
845-
const key = this._getExpansionKey(dataNode);
846-
const startIndex = this._flattenedNodes.value.findIndex(
847-
node => this._getExpansionKey(node) === key,
872+
const results = this._findChildrenByLevel(
873+
this.levelAccessor,
874+
this._flattenedNodes.value,
875+
dataNode,
876+
Infinity,
848877
);
849-
const results: T[] = [];
850-
851-
// Goes through flattened tree nodes in the `dataNodes` array, and get all descendants.
852-
// The level of descendants of a tree node must be greater than the level of the given
853-
// tree node.
854-
// If we reach a node whose level is equal to the level of the tree node, we hit a sibling.
855-
// If we reach a node whose level is greater than the level of the tree node, we hit a
856-
// sibling of an ancestor.
857-
const currentLevel = this.levelAccessor(dataNode);
858-
for (
859-
let i = startIndex + 1;
860-
i < this._flattenedNodes.value.length &&
861-
currentLevel < this.levelAccessor(this._flattenedNodes.value[i]);
862-
i++
863-
) {
864-
results.push(this._flattenedNodes.value[i]);
865-
}
866878
return observableOf(results);
867879
}
868880
if (this.childrenAccessor) {
@@ -1003,7 +1015,7 @@ export class CdkTree<T, K = T>
10031015
*
10041016
* This also computes parent, level, and group data.
10051017
*/
1006-
private _convertData(
1018+
private _computeRenderingData(
10071019
nodes: readonly T[],
10081020
nodeType: 'flat' | 'nested',
10091021
): Observable<{

0 commit comments

Comments
 (0)