blob: c4bd7d1c74152d971188a68aa64702da4973098d [file] [log] [blame]
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { SelectionModel, isDataSource } from '@angular/cdk/collections';
import { Observable, BehaviorSubject, of, Subject } from 'rxjs';
import { take, filter, takeUntil } from 'rxjs/operators';
import { Directive, Inject, InjectionToken, Optional, ViewContainerRef, TemplateRef, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, ElementRef, Input, IterableDiffers, ViewChild, ViewEncapsulation, Renderer2, HostListener, NgModule } from '@angular/core';
import { Directionality } from '@angular/cdk/bidi';
import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion';
import { FocusMonitor } from '@angular/cdk/a11y';
import { CommonModule } from '@angular/common';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Base tree control. It has basic toggle/expand/collapse operations on a single data node.
* @abstract
* @template T
*/
class BaseTreeControl {
constructor() {
/**
* A selection model with multi-selection to track expansion status.
*/
this.expansionModel = new SelectionModel(true);
}
/**
* Toggles one single data node's expanded/collapsed state.
* @param {?} dataNode
* @return {?}
*/
toggle(dataNode) {
this.expansionModel.toggle(dataNode);
}
/**
* Expands one single data node.
* @param {?} dataNode
* @return {?}
*/
expand(dataNode) {
this.expansionModel.select(dataNode);
}
/**
* Collapses one single data node.
* @param {?} dataNode
* @return {?}
*/
collapse(dataNode) {
this.expansionModel.deselect(dataNode);
}
/**
* Whether a given data node is expanded or not. Returns true if the data node is expanded.
* @param {?} dataNode
* @return {?}
*/
isExpanded(dataNode) {
return this.expansionModel.isSelected(dataNode);
}
/**
* Toggles a subtree rooted at `node` recursively.
* @param {?} dataNode
* @return {?}
*/
toggleDescendants(dataNode) {
this.expansionModel.isSelected(dataNode)
? this.collapseDescendants(dataNode)
: this.expandDescendants(dataNode);
}
/**
* Collapse all dataNodes in the tree.
* @return {?}
*/
collapseAll() {
this.expansionModel.clear();
}
/**
* Expands a subtree rooted at given data node recursively.
* @param {?} dataNode
* @return {?}
*/
expandDescendants(dataNode) {
/** @type {?} */
let toBeProcessed = [dataNode];
toBeProcessed.push(...this.getDescendants(dataNode));
this.expansionModel.select(...toBeProcessed);
}
/**
* Collapses a subtree rooted at given data node recursively.
* @param {?} dataNode
* @return {?}
*/
collapseDescendants(dataNode) {
/** @type {?} */
let toBeProcessed = [dataNode];
toBeProcessed.push(...this.getDescendants(dataNode));
this.expansionModel.deselect(...toBeProcessed);
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Flat tree control. Able to expand/collapse a subtree recursively for flattened tree.
* @template T
*/
class FlatTreeControl extends BaseTreeControl {
/**
* Construct with flat tree data node functions getLevel and isExpandable.
* @param {?} getLevel
* @param {?} isExpandable
*/
constructor(getLevel, isExpandable) {
super();
this.getLevel = getLevel;
this.isExpandable = isExpandable;
}
/**
* Gets a list of the data node's subtree of descendent data nodes.
*
* To make this working, the `dataNodes` of the TreeControl must be flattened tree nodes
* with correct levels.
* @param {?} dataNode
* @return {?}
*/
getDescendants(dataNode) {
/** @type {?} */
const startIndex = this.dataNodes.indexOf(dataNode);
/** @type {?} */
const results = [];
// Goes through flattened tree nodes in the `dataNodes` array, and get all descendants.
// The level of descendants of a tree node must be greater than the level of the given
// tree node.
// If we reach a node whose level is equal to the level of the tree node, we hit a sibling.
// If we reach a node whose level is greater than the level of the tree node, we hit a
// sibling of an ancestor.
for (let i = startIndex + 1; i < this.dataNodes.length && this.getLevel(dataNode) < this.getLevel(this.dataNodes[i]); i++) {
results.push(this.dataNodes[i]);
}
return results;
}
/**
* Expands all data nodes in the tree.
*
* To make this working, the `dataNodes` variable of the TreeControl must be set to all flattened
* data nodes of the tree.
* @return {?}
*/
expandAll() {
this.expansionModel.select(...this.dataNodes);
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Nested tree control. Able to expand/collapse a subtree recursively for NestedNode type.
* @template T
*/
class NestedTreeControl extends BaseTreeControl {
/**
* Construct with nested tree function getChildren.
* @param {?} getChildren
*/
constructor(getChildren) {
super();
this.getChildren = getChildren;
}
/**
* Expands all dataNodes in the tree.
*
* To make this working, the `dataNodes` variable of the TreeControl must be set to all root level
* data nodes of the tree.
* @return {?}
*/
expandAll() {
this.expansionModel.clear();
/** @type {?} */
const allNodes = this.dataNodes.reduce((/**
* @param {?} accumulator
* @param {?} dataNode
* @return {?}
*/
(accumulator, dataNode) => [...accumulator, ...this.getDescendants(dataNode), dataNode]), []);
this.expansionModel.select(...allNodes);
}
/**
* Gets a list of descendant dataNodes of a subtree rooted at given data node recursively.
* @param {?} dataNode
* @return {?}
*/
getDescendants(dataNode) {
/** @type {?} */
const descendants = [];
this._getDescendants(descendants, dataNode);
// Remove the node itself
return descendants.splice(1);
}
/**
* A helper function to get descendants recursively.
* @protected
* @param {?} descendants
* @param {?} dataNode
* @return {?}
*/
_getDescendants(descendants, dataNode) {
descendants.push(dataNode);
/** @type {?} */
const childrenNodes = this.getChildren(dataNode);
if (Array.isArray(childrenNodes)) {
childrenNodes.forEach((/**
* @param {?} child
* @return {?}
*/
(child) => this._getDescendants(descendants, child)));
}
else if (childrenNodes instanceof Observable) {
// TypeScript as of version 3.5 doesn't seem to treat `Boolean` like a function that
// returns a `boolean` specifically in the context of `filter`, so we manually clarify that.
childrenNodes.pipe(take(1), filter((/** @type {?} */ (Boolean))))
.subscribe((/**
* @param {?} children
* @return {?}
*/
children => {
for (const child of children) {
this._getDescendants(descendants, child);
}
}));
}
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Injection token used to provide a `CdkTreeNode` to its outlet.
* Used primarily to avoid circular imports.
* \@docs-private
* @type {?}
*/
const CDK_TREE_NODE_OUTLET_NODE = new InjectionToken('CDK_TREE_NODE_OUTLET_NODE');
/**
* Outlet for nested CdkNode. Put `[cdkTreeNodeOutlet]` on a tag to place children dataNodes
* inside the outlet.
*/
class CdkTreeNodeOutlet {
/**
* @param {?} viewContainer
* @param {?=} _node
*/
constructor(viewContainer, _node) {
this.viewContainer = viewContainer;
this._node = _node;
}
}
CdkTreeNodeOutlet.decorators = [
{ type: Directive, args: [{
selector: '[cdkTreeNodeOutlet]'
},] },
];
/** @nocollapse */
CdkTreeNodeOutlet.ctorParameters = () => [
{ type: ViewContainerRef },
{ type: undefined, decorators: [{ type: Inject, args: [CDK_TREE_NODE_OUTLET_NODE,] }, { type: Optional }] }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Context provided to the tree node component.
* @template T
*/
class CdkTreeNodeOutletContext {
/**
* @param {?} data
*/
constructor(data) {
this.$implicit = data;
}
}
/**
* Data node definition for the CdkTree.
* Captures the node's template and a when predicate that describes when this node should be used.
* @template T
*/
class CdkTreeNodeDef {
/**
* \@docs-private
* @param {?} template
*/
constructor(template) {
this.template = template;
}
}
CdkTreeNodeDef.decorators = [
{ type: Directive, args: [{
selector: '[cdkTreeNodeDef]',
inputs: [
'when: cdkTreeNodeDefWhen'
],
},] },
];
/** @nocollapse */
CdkTreeNodeDef.ctorParameters = () => [
{ type: TemplateRef }
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Returns an error to be thrown when there is no usable data.
* \@docs-private
* @return {?}
*/
function getTreeNoValidDataSourceError() {
return Error(`A valid data source must be provided.`);
}
/**
* Returns an error to be thrown when there are multiple nodes that are missing a when function.
* \@docs-private
* @return {?}
*/
function getTreeMultipleDefaultNodeDefsError() {
return Error(`There can only be one default row without a when predicate function.`);
}
/**
* Returns an error to be thrown when there are no matching node defs for a particular set of data.
* \@docs-private
* @return {?}
*/
function getTreeMissingMatchingNodeDefError() {
return Error(`Could not find a matching node definition for the provided node data.`);
}
/**
* Returns an error to be thrown when there are tree control.
* \@docs-private
* @return {?}
*/
function getTreeControlMissingError() {
return Error(`Could not find a tree control for the tree.`);
}
/**
* Returns an error to be thrown when tree control did not implement functions for flat/nested node.
* \@docs-private
* @return {?}
*/
function getTreeControlFunctionsMissingError() {
return Error(`Could not find functions for nested/flat tree in tree control.`);
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* CDK tree component that connects with a data source to retrieve data of type `T` and renders
* dataNodes with hierarchy. Updates the dataNodes when new data is provided by the data source.
* @template T
*/
class CdkTree {
/**
* @param {?} _differs
* @param {?} _changeDetectorRef
*/
constructor(_differs, _changeDetectorRef) {
this._differs = _differs;
this._changeDetectorRef = _changeDetectorRef;
/**
* Subject that emits when the component has been destroyed.
*/
this._onDestroy = new Subject();
/**
* Level of nodes
*/
this._levels = new Map();
// TODO(tinayuangao): Setup a listener for scrolling, emit the calculated view to viewChange.
// Remove the MAX_VALUE in viewChange
/**
* Stream containing the latest information on what rows are being displayed on screen.
* Can be used by the data source to as a heuristic of what data should be provided.
*/
this.viewChange = new BehaviorSubject({ start: 0, end: Number.MAX_VALUE });
}
/**
* Provides a stream containing the latest data array to render. Influenced by the tree's
* stream of view window (what dataNodes are currently on screen).
* Data source can be an observable of data array, or a data array to render.
* @return {?}
*/
get dataSource() { return this._dataSource; }
/**
* @param {?} dataSource
* @return {?}
*/
set dataSource(dataSource) {
if (this._dataSource !== dataSource) {
this._switchDataSource(dataSource);
}
}
/**
* @return {?}
*/
ngOnInit() {
this._dataDiffer = this._differs.find([]).create(this.trackBy);
if (!this.treeControl) {
throw getTreeControlMissingError();
}
}
/**
* @return {?}
*/
ngOnDestroy() {
this._nodeOutlet.viewContainer.clear();
this._onDestroy.next();
this._onDestroy.complete();
if (this._dataSource && typeof ((/** @type {?} */ (this._dataSource))).disconnect === 'function') {
((/** @type {?} */ (this.dataSource))).disconnect(this);
}
if (this._dataSubscription) {
this._dataSubscription.unsubscribe();
this._dataSubscription = null;
}
}
/**
* @return {?}
*/
ngAfterContentChecked() {
/** @type {?} */
const defaultNodeDefs = this._nodeDefs.filter((/**
* @param {?} def
* @return {?}
*/
def => !def.when));
if (defaultNodeDefs.length > 1) {
throw getTreeMultipleDefaultNodeDefsError();
}
this._defaultNodeDef = defaultNodeDefs[0];
if (this.dataSource && this._nodeDefs && !this._dataSubscription) {
this._observeRenderChanges();
}
}
// TODO(tinayuangao): Work on keyboard traversal and actions, make sure it's working for RTL
// and nested trees.
/**
* Switch to the provided data source by resetting the data and unsubscribing from the current
* render change subscription if one exists. If the data source is null, interpret this by
* clearing the node outlet. Otherwise start listening for new data.
* @private
* @param {?} dataSource
* @return {?}
*/
_switchDataSource(dataSource) {
if (this._dataSource && typeof ((/** @type {?} */ (this._dataSource))).disconnect === 'function') {
((/** @type {?} */ (this.dataSource))).disconnect(this);
}
if (this._dataSubscription) {
this._dataSubscription.unsubscribe();
this._dataSubscription = null;
}
// Remove the all dataNodes if there is now no data source
if (!dataSource) {
this._nodeOutlet.viewContainer.clear();
}
this._dataSource = dataSource;
if (this._nodeDefs) {
this._observeRenderChanges();
}
}
/**
* Set up a subscription for the data provided by the data source.
* @private
* @return {?}
*/
_observeRenderChanges() {
/** @type {?} */
let dataStream;
if (isDataSource(this._dataSource)) {
dataStream = this._dataSource.connect(this);
}
else if (this._dataSource instanceof Observable) {
dataStream = this._dataSource;
}
else if (Array.isArray(this._dataSource)) {
dataStream = of(this._dataSource);
}
if (dataStream) {
this._dataSubscription = dataStream.pipe(takeUntil(this._onDestroy))
.subscribe((/**
* @param {?} data
* @return {?}
*/
data => this.renderNodeChanges(data)));
}
else {
throw getTreeNoValidDataSourceError();
}
}
/**
* Check for changes made in the data and render each change (node added/removed/moved).
* @param {?} data
* @param {?=} dataDiffer
* @param {?=} viewContainer
* @param {?=} parentData
* @return {?}
*/
renderNodeChanges(data, dataDiffer = this._dataDiffer, viewContainer = this._nodeOutlet.viewContainer, parentData) {
/** @type {?} */
const changes = dataDiffer.diff(data);
if (!changes) {
return;
}
changes.forEachOperation((/**
* @param {?} item
* @param {?} adjustedPreviousIndex
* @param {?} currentIndex
* @return {?}
*/
(item, adjustedPreviousIndex, currentIndex) => {
if (item.previousIndex == null) {
this.insertNode(data[(/** @type {?} */ (currentIndex))], (/** @type {?} */ (currentIndex)), viewContainer, parentData);
}
else if (currentIndex == null) {
viewContainer.remove((/** @type {?} */ (adjustedPreviousIndex)));
this._levels.delete(item.item);
}
else {
/** @type {?} */
const view = viewContainer.get((/** @type {?} */ (adjustedPreviousIndex)));
viewContainer.move((/** @type {?} */ (view)), currentIndex);
}
}));
this._changeDetectorRef.detectChanges();
}
/**
* Finds the matching node definition that should be used for this node data. If there is only
* one node definition, it is returned. Otherwise, find the node definition that has a when
* predicate that returns true with the data. If none return true, return the default node
* definition.
* @param {?} data
* @param {?} i
* @return {?}
*/
_getNodeDef(data, i) {
if (this._nodeDefs.length === 1) {
return this._nodeDefs.first;
}
/** @type {?} */
const nodeDef = this._nodeDefs.find((/**
* @param {?} def
* @return {?}
*/
def => def.when && def.when(i, data))) || this._defaultNodeDef;
if (!nodeDef) {
throw getTreeMissingMatchingNodeDefError();
}
return nodeDef;
}
/**
* Create the embedded view for the data node template and place it in the correct index location
* within the data node view container.
* @param {?} nodeData
* @param {?} index
* @param {?=} viewContainer
* @param {?=} parentData
* @return {?}
*/
insertNode(nodeData, index, viewContainer, parentData) {
/** @type {?} */
const node = this._getNodeDef(nodeData, index);
// Node context that will be provided to created embedded view
/** @type {?} */
const context = new CdkTreeNodeOutletContext(nodeData);
// If the tree is flat tree, then use the `getLevel` function in flat tree control
// Otherwise, use the level of parent node.
if (this.treeControl.getLevel) {
context.level = this.treeControl.getLevel(nodeData);
}
else if (typeof parentData !== 'undefined' && this._levels.has(parentData)) {
context.level = (/** @type {?} */ (this._levels.get(parentData))) + 1;
}
else {
context.level = 0;
}
this._levels.set(nodeData, context.level);
// Use default tree nodeOutlet, or nested node's nodeOutlet
/** @type {?} */
const container = viewContainer ? viewContainer : this._nodeOutlet.viewContainer;
container.createEmbeddedView(node.template, context, index);
// Set the data to just created `CdkTreeNode`.
// The `CdkTreeNode` created from `createEmbeddedView` will be saved in static variable
// `mostRecentTreeNode`. We get it from static variable and pass the node data to it.
if (CdkTreeNode.mostRecentTreeNode) {
CdkTreeNode.mostRecentTreeNode.data = nodeData;
}
}
}
CdkTree.decorators = [
{ type: Component, args: [{selector: 'cdk-tree',
exportAs: 'cdkTree',
template: `<ng-container cdkTreeNodeOutlet></ng-container>`,
host: {
'class': 'cdk-tree',
'role': 'tree',
},
encapsulation: ViewEncapsulation.None,
// The "OnPush" status for the `CdkTree` component is effectively a noop, so we are removing it.
// The view for `CdkTree` consists entirely of templates declared in other views. As they are
// declared elsewhere, they are checked when their declaration points are checked.
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default
},] },
];
/** @nocollapse */
CdkTree.ctorParameters = () => [
{ type: IterableDiffers },
{ type: ChangeDetectorRef }
];
CdkTree.propDecorators = {
dataSource: [{ type: Input }],
treeControl: [{ type: Input }],
trackBy: [{ type: Input }],
_nodeOutlet: [{ type: ViewChild, args: [CdkTreeNodeOutlet, { static: true },] }],
_nodeDefs: [{ type: ContentChildren, args: [CdkTreeNodeDef,] }]
};
/**
* Tree node for CdkTree. It contains the data in the tree node.
* @template T
*/
class CdkTreeNode {
/**
* @param {?} _elementRef
* @param {?} _tree
*/
constructor(_elementRef, _tree) {
this._elementRef = _elementRef;
this._tree = _tree;
/**
* Subject that emits when the component has been destroyed.
*/
this._destroyed = new Subject();
/**
* Emits when the node's data has changed.
*/
this._dataChanges = new Subject();
/**
* The role of the node should be 'group' if it's an internal node,
* and 'treeitem' if it's a leaf node.
*/
this.role = 'treeitem';
CdkTreeNode.mostRecentTreeNode = (/** @type {?} */ (this));
}
/**
* The tree node's data.
* @return {?}
*/
get data() { return this._data; }
/**
* @param {?} value
* @return {?}
*/
set data(value) {
if (value !== this._data) {
this._data = value;
this._setRoleFromData();
this._dataChanges.next();
}
}
/**
* @return {?}
*/
get isExpanded() {
return this._tree.treeControl.isExpanded(this._data);
}
/**
* @return {?}
*/
get level() {
return this._tree.treeControl.getLevel ? this._tree.treeControl.getLevel(this._data) : 0;
}
/**
* @return {?}
*/
ngOnDestroy() {
// If this is the last tree node being destroyed,
// clear out the reference to avoid leaking memory.
if (CdkTreeNode.mostRecentTreeNode === this) {
CdkTreeNode.mostRecentTreeNode = null;
}
this._dataChanges.complete();
this._destroyed.next();
this._destroyed.complete();
}
/**
* Focuses the menu item. Implements for FocusableOption.
* @return {?}
*/
focus() {
this._elementRef.nativeElement.focus();
}
/**
* @protected
* @return {?}
*/
_setRoleFromData() {
if (this._tree.treeControl.isExpandable) {
this.role = this._tree.treeControl.isExpandable(this._data) ? 'group' : 'treeitem';
}
else {
if (!this._tree.treeControl.getChildren) {
throw getTreeControlFunctionsMissingError();
}
/** @type {?} */
const childrenNodes = this._tree.treeControl.getChildren(this._data);
if (Array.isArray(childrenNodes)) {
this._setRoleFromChildren((/** @type {?} */ (childrenNodes)));
}
else if (childrenNodes instanceof Observable) {
childrenNodes.pipe(takeUntil(this._destroyed))
.subscribe((/**
* @param {?} children
* @return {?}
*/
children => this._setRoleFromChildren(children)));
}
}
}
/**
* @protected
* @param {?} children
* @return {?}
*/
_setRoleFromChildren(children) {
this.role = children && children.length ? 'group' : 'treeitem';
}
}
/**
* The most recently created `CdkTreeNode`. We save it in static variable so we can retrieve it
* in `CdkTree` and set the data to it.
*/
CdkTreeNode.mostRecentTreeNode = null;
CdkTreeNode.decorators = [
{ type: Directive, args: [{
selector: 'cdk-tree-node',
exportAs: 'cdkTreeNode',
host: {
'[attr.aria-expanded]': 'isExpanded',
'[attr.aria-level]': 'role === "treeitem" ? level : null',
'[attr.role]': 'role',
'class': 'cdk-tree-node',
},
},] },
];
/** @nocollapse */
CdkTreeNode.ctorParameters = () => [
{ type: ElementRef },
{ type: CdkTree }
];
CdkTreeNode.propDecorators = {
role: [{ type: Input }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Nested node is a child of `<cdk-tree>`. It works with nested tree.
* By using `cdk-nested-tree-node` component in tree node template, children of the parent node will
* be added in the `cdkTreeNodeOutlet` in tree node template.
* For example:
* ```html
* <cdk-nested-tree-node>
* {{node.name}}
* <ng-template cdkTreeNodeOutlet></ng-template>
* </cdk-nested-tree-node>
* ```
* The children of node will be automatically added to `cdkTreeNodeOutlet`, the result dom will be
* like this:
* ```html
* <cdk-nested-tree-node>
* {{node.name}}
* <cdk-nested-tree-node>{{child1.name}}</cdk-nested-tree-node>
* <cdk-nested-tree-node>{{child2.name}}</cdk-nested-tree-node>
* </cdk-nested-tree-node>
* ```
* @template T
*/
class CdkNestedTreeNode extends CdkTreeNode {
/**
* @param {?} _elementRef
* @param {?} _tree
* @param {?} _differs
*/
constructor(_elementRef, _tree, _differs) {
super(_elementRef, _tree);
this._elementRef = _elementRef;
this._tree = _tree;
this._differs = _differs;
}
/**
* @return {?}
*/
ngAfterContentInit() {
this._dataDiffer = this._differs.find([]).create(this._tree.trackBy);
if (!this._tree.treeControl.getChildren) {
throw getTreeControlFunctionsMissingError();
}
/** @type {?} */
const childrenNodes = this._tree.treeControl.getChildren(this.data);
if (Array.isArray(childrenNodes)) {
this.updateChildrenNodes((/** @type {?} */ (childrenNodes)));
}
else if (childrenNodes instanceof Observable) {
childrenNodes.pipe(takeUntil(this._destroyed))
.subscribe((/**
* @param {?} result
* @return {?}
*/
result => this.updateChildrenNodes(result)));
}
this.nodeOutlet.changes.pipe(takeUntil(this._destroyed))
.subscribe((/**
* @return {?}
*/
() => this.updateChildrenNodes()));
}
/**
* @return {?}
*/
ngOnDestroy() {
this._clear();
super.ngOnDestroy();
}
/**
* Add children dataNodes to the NodeOutlet
* @protected
* @param {?=} children
* @return {?}
*/
updateChildrenNodes(children) {
/** @type {?} */
const outlet = this._getNodeOutlet();
if (children) {
this._children = children;
}
if (outlet && this._children) {
/** @type {?} */
const viewContainer = outlet.viewContainer;
this._tree.renderNodeChanges(this._children, this._dataDiffer, viewContainer, this._data);
}
else {
// Reset the data differ if there's no children nodes displayed
this._dataDiffer.diff([]);
}
}
/**
* Clear the children dataNodes.
* @protected
* @return {?}
*/
_clear() {
/** @type {?} */
const outlet = this._getNodeOutlet();
if (outlet) {
outlet.viewContainer.clear();
this._dataDiffer.diff([]);
}
}
/**
* Gets the outlet for the current node.
* @private
* @return {?}
*/
_getNodeOutlet() {
/** @type {?} */
const outlets = this.nodeOutlet;
if (outlets) {
// Note that since we use `descendants: true` on the query, we have to ensure
// that we don't pick up the outlet of a child node by accident.
return outlets.find((/**
* @param {?} outlet
* @return {?}
*/
outlet => !outlet._node || outlet._node === this));
}
}
}
CdkNestedTreeNode.decorators = [
{ type: Directive, args: [{
selector: 'cdk-nested-tree-node',
exportAs: 'cdkNestedTreeNode',
host: {
'[attr.aria-expanded]': 'isExpanded',
'[attr.role]': 'role',
'class': 'cdk-tree-node cdk-nested-tree-node',
},
providers: [
{ provide: CdkTreeNode, useExisting: CdkNestedTreeNode },
{ provide: CDK_TREE_NODE_OUTLET_NODE, useExisting: CdkNestedTreeNode }
]
},] },
];
/** @nocollapse */
CdkNestedTreeNode.ctorParameters = () => [
{ type: ElementRef },
{ type: CdkTree },
{ type: IterableDiffers }
];
CdkNestedTreeNode.propDecorators = {
nodeOutlet: [{ type: ContentChildren, args: [CdkTreeNodeOutlet, {
// We need to use `descendants: true`, because Ivy will no longer match
// indirect descendants if it's left as false.
descendants: true
},] }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Regex used to split a string on its CSS units.
* @type {?}
*/
const cssUnitPattern = /([A-Za-z%]+)$/;
/**
* Indent for the children tree dataNodes.
* This directive will add left-padding to the node to show hierarchy.
* @template T
*/
class CdkTreeNodePadding {
/**
* @param {?} _treeNode
* @param {?} _tree
* @param {?} _renderer
* @param {?} _element
* @param {?} _dir
*/
constructor(_treeNode, _tree, _renderer, _element, _dir) {
this._treeNode = _treeNode;
this._tree = _tree;
this._renderer = _renderer;
this._element = _element;
this._dir = _dir;
/**
* Subject that emits when the component has been destroyed.
*/
this._destroyed = new Subject();
/**
* CSS units used for the indentation value.
*/
this.indentUnits = 'px';
this._indent = 40;
this._setPadding();
if (_dir) {
_dir.change.pipe(takeUntil(this._destroyed)).subscribe((/**
* @return {?}
*/
() => this._setPadding(true)));
}
// In Ivy the indentation binding might be set before the tree node's data has been added,
// which means that we'll miss the first render. We have to subscribe to changes in the
// data to ensure that everything is up to date.
_treeNode._dataChanges.subscribe((/**
* @return {?}
*/
() => this._setPadding()));
}
/**
* The level of depth of the tree node. The padding will be `level * indent` pixels.
* @return {?}
*/
get level() { return this._level; }
/**
* @param {?} value
* @return {?}
*/
set level(value) {
this._level = coerceNumberProperty(value);
this._setPadding();
}
/**
* The indent for each level. Can be a number or a CSS string.
* Default number 40px from material design menu sub-menu spec.
* @return {?}
*/
get indent() { return this._indent; }
/**
* @param {?} indent
* @return {?}
*/
set indent(indent) {
/** @type {?} */
let value = indent;
/** @type {?} */
let units = 'px';
if (typeof indent === 'string') {
/** @type {?} */
const parts = indent.split(cssUnitPattern);
value = parts[0];
units = parts[1] || units;
}
this.indentUnits = units;
this._indent = coerceNumberProperty(value);
this._setPadding();
}
/**
* @return {?}
*/
ngOnDestroy() {
this._destroyed.next();
this._destroyed.complete();
}
/**
* The padding indent value for the tree node. Returns a string with px numbers if not null.
* @return {?}
*/
_paddingIndent() {
/** @type {?} */
const nodeLevel = (this._treeNode.data && this._tree.treeControl.getLevel)
? this._tree.treeControl.getLevel(this._treeNode.data)
: null;
/** @type {?} */
const level = this._level || nodeLevel;
return level ? `${level * this._indent}${this.indentUnits}` : null;
}
/**
* @param {?=} forceChange
* @return {?}
*/
_setPadding(forceChange = false) {
/** @type {?} */
const padding = this._paddingIndent();
if (padding !== this._currentPadding || forceChange) {
/** @type {?} */
const element = this._element.nativeElement;
/** @type {?} */
const paddingProp = this._dir && this._dir.value === 'rtl' ? 'paddingRight' : 'paddingLeft';
/** @type {?} */
const resetProp = paddingProp === 'paddingLeft' ? 'paddingRight' : 'paddingLeft';
this._renderer.setStyle(element, paddingProp, padding);
this._renderer.setStyle(element, resetProp, null);
this._currentPadding = padding;
}
}
}
CdkTreeNodePadding.decorators = [
{ type: Directive, args: [{
selector: '[cdkTreeNodePadding]',
},] },
];
/** @nocollapse */
CdkTreeNodePadding.ctorParameters = () => [
{ type: CdkTreeNode },
{ type: CdkTree },
{ type: Renderer2 },
{ type: ElementRef },
{ type: Directionality, decorators: [{ type: Optional }] }
];
CdkTreeNodePadding.propDecorators = {
level: [{ type: Input, args: ['cdkTreeNodePadding',] }],
indent: [{ type: Input, args: ['cdkTreeNodePaddingIndent',] }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Node toggle to expand/collapse the node.
* @template T
*/
class CdkTreeNodeToggle {
/**
* @param {?} _tree
* @param {?} _treeNode
*/
constructor(_tree, _treeNode) {
this._tree = _tree;
this._treeNode = _treeNode;
this._recursive = false;
}
/**
* Whether expand/collapse the node recursively.
* @return {?}
*/
get recursive() { return this._recursive; }
/**
* @param {?} value
* @return {?}
*/
set recursive(value) { this._recursive = coerceBooleanProperty(value); }
// We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
// In Ivy the `host` bindings will be merged when this class is extended, whereas in
// ViewEngine they're overwritten.
// TODO(crisbeto): we move this back into `host` once Ivy is turned on by default.
// tslint:disable-next-line:no-host-decorator-in-concrete
/**
* @param {?} event
* @return {?}
*/
_toggle(event) {
this.recursive
? this._tree.treeControl.toggleDescendants(this._treeNode.data)
: this._tree.treeControl.toggle(this._treeNode.data);
event.stopPropagation();
}
}
CdkTreeNodeToggle.decorators = [
{ type: Directive, args: [{ selector: '[cdkTreeNodeToggle]' },] },
];
/** @nocollapse */
CdkTreeNodeToggle.ctorParameters = () => [
{ type: CdkTree },
{ type: CdkTreeNode }
];
CdkTreeNodeToggle.propDecorators = {
recursive: [{ type: Input, args: ['cdkTreeNodeToggleRecursive',] }],
_toggle: [{ type: HostListener, args: ['click', ['$event'],] }]
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/** @type {?} */
const EXPORTED_DECLARATIONS = [
CdkNestedTreeNode,
CdkTreeNodeDef,
CdkTreeNodePadding,
CdkTreeNodeToggle,
CdkTree,
CdkTreeNode,
CdkTreeNodeOutlet,
];
class CdkTreeModule {
}
CdkTreeModule.decorators = [
{ type: NgModule, args: [{
imports: [CommonModule],
exports: EXPORTED_DECLARATIONS,
declarations: EXPORTED_DECLARATIONS,
providers: [FocusMonitor, CdkTreeNodeDef]
},] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
export { BaseTreeControl, FlatTreeControl, NestedTreeControl, CdkNestedTreeNode, CdkTreeNodeOutletContext, CdkTreeNodeDef, CdkTreeNodePadding, CDK_TREE_NODE_OUTLET_NODE, CdkTreeNodeOutlet, CdkTree, CdkTreeNode, getTreeNoValidDataSourceError, getTreeMultipleDefaultNodeDefsError, getTreeMissingMatchingNodeDefError, getTreeControlMissingError, getTreeControlFunctionsMissingError, CdkTreeModule, CdkTreeNodeToggle };
//# sourceMappingURL=tree.js.map