Skip to content

Commit

Permalink
feat(module:tree-view): add tree-view component (#6161)
Browse files Browse the repository at this point in the history
close #5976, close #5809, close #5739, close #5736, close #5519, close #5446, close #5152, close #4694, close #4472, close #3832, close #2785, close #2744, close #6199
  • Loading branch information
hsuanxyz committed Dec 16, 2020
1 parent 2f79ef1 commit 05d58de
Show file tree
Hide file tree
Showing 48 changed files with 2,561 additions and 40 deletions.
1 change: 1 addition & 0 deletions components/components.less
Expand Up @@ -54,6 +54,7 @@
@import "./upload/style/entry.less";
@import "./auto-complete/style/entry.less";
@import "./cascader/style/entry.less";
@import "./tree-view/style/entry.less";
@import "./tree/style/entry.less";
@import "./tree-select/style/entry.less";
@import "./calendar/style/entry.less";
Expand Down
15 changes: 10 additions & 5 deletions components/core/animation/collapse.ts
Expand Up @@ -19,17 +19,22 @@ export const collapseMotion: AnimationTriggerMetadata = trigger('collapseMotion'
export const treeCollapseMotion: AnimationTriggerMetadata = trigger('treeCollapseMotion', [
transition('* => *', [
query(
'nz-tree-node:leave',
[style({ overflow: 'hidden' }), stagger(0, [animate(`150ms ${AnimationCurves.EASE_IN_OUT}`, style({ height: 0 }))])],
'nz-tree-node:leave,nz-tree-builtin-node:leave',
[
style({ overflow: 'hidden' }),
stagger(0, [animate(`150ms ${AnimationCurves.EASE_IN_OUT}`, style({ height: 0, opacity: 0, 'padding-bottom': 0 }))])
],
{
optional: true
}
),
query(
'nz-tree-node:enter',
'nz-tree-node:enter,nz-tree-builtin-node:enter',
[
style({ overflow: 'hidden', height: 0 }),
stagger(0, [animate(`150ms ${AnimationCurves.EASE_IN_OUT}`, style({ overflow: 'hidden', height: '*' }))])
style({ overflow: 'hidden', height: 0, opacity: 0, 'padding-bottom': 0 }),
stagger(0, [
animate(`150ms ${AnimationCurves.EASE_IN_OUT}`, style({ overflow: 'hidden', height: '*', opacity: '*', 'padding-bottom': '*' }))
])
],
{
optional: true
Expand Down
14 changes: 14 additions & 0 deletions components/style/patch.less
Expand Up @@ -11,6 +11,20 @@
z-index: 1000;
}

.cdk-visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
outline: 0;
-webkit-appearance: none;
-moz-appearance: none;
}

.cdk-overlay-backdrop {
top: 0;
bottom: 0;
Expand Down
2 changes: 1 addition & 1 deletion components/tree-select/style/patch.less
@@ -1,4 +1,4 @@
.ant-tree.ant-select-tree.ant-tree-show-line nz-tree-node:not(:last-child) > li::before {
.ant-tree.ant-select-tree.ant-tree-show-line nz-tree-node[builtin]:not(:last-child) > li::before {
content: ' ';
width: 1px;
border-left: 1px solid #d9d9d9;
Expand Down
4 changes: 2 additions & 2 deletions components/tree-select/tree-select.spec.ts
Expand Up @@ -325,7 +325,7 @@ describe('tree-select component', () => {
treeSelect.nativeElement.click();
fixture.detectChanges();
expect(treeSelectComponent.nzOpen).toBe(true);
node = overlayContainerElement.querySelector('nz-tree-node')!;
node = overlayContainerElement.querySelector('nz-tree-node[builtin]')!;
dispatchMouseEvent(node, 'click');
fixture.detectChanges();
flush();
Expand Down Expand Up @@ -447,7 +447,7 @@ describe('tree-select component', () => {
fixture.detectChanges();
expect(treeSelectComponent.nzOpen).toBe(true);
fixture.detectChanges();
const targetNode = overlayContainerElement.querySelectorAll('nz-tree-node')[2];
const targetNode = overlayContainerElement.querySelectorAll('nz-tree-node[builtin]')[2];
dispatchMouseEvent(targetNode, 'click');
fixture.detectChanges();
flush();
Expand Down
39 changes: 39 additions & 0 deletions components/tree-view/checkbox.ts
@@ -0,0 +1,39 @@
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/

import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';

import { BooleanInput } from 'ng-zorro-antd/core/types';
import { InputBoolean } from 'ng-zorro-antd/core/util';

@Component({
selector: 'nz-tree-node-checkbox:not([builtin])',
template: `
<span class="ant-tree-checkbox-inner"></span>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
preserveWhitespaces: false,
host: {
class: 'ant-tree-checkbox',
'[class.ant-tree-checkbox-checked]': `nzChecked`,
'[class.ant-tree-checkbox-indeterminate]': `nzIndeterminate`,
'[class.ant-tree-checkbox-disabled]': `nzDisabled`,
'(click)': 'onClick($event)'
}
})
export class NzTreeNodeCheckboxComponent {
static ngAcceptInputType_nzDisabled: BooleanInput;

@Input() nzChecked?: boolean;
@Input() nzIndeterminate?: boolean;
@Input() @InputBoolean() nzDisabled?: boolean;
@Output() readonly nzClick = new EventEmitter<MouseEvent>();

onClick(e: MouseEvent): void {
if (!this.nzDisabled) {
this.nzClick.emit(e);
}
}
}
122 changes: 122 additions & 0 deletions components/tree-view/data-source.ts
@@ -0,0 +1,122 @@
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/

import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { FlatTreeControl, TreeControl } from '@angular/cdk/tree';
import { BehaviorSubject, merge, Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';

export class NzTreeFlattener<T, F, K = F> {
constructor(
public transformFunction: (node: T, level: number) => F,
public getLevel: (node: F) => number,
public isExpandable: (node: F) => boolean,
public getChildren: (node: T) => Observable<T[]> | T[] | undefined | null
) {}

private flattenNode(node: T, level: number, resultNodes: F[], parentMap: boolean[]): F[] {
const flatNode = this.transformFunction(node, level);
resultNodes.push(flatNode);

if (this.isExpandable(flatNode)) {
const childrenNodes = this.getChildren(node);
if (childrenNodes) {
if (Array.isArray(childrenNodes)) {
this.flattenChildren(childrenNodes, level, resultNodes, parentMap);
} else {
childrenNodes.pipe(take(1)).subscribe(children => {
this.flattenChildren(children, level, resultNodes, parentMap);
});
}
}
}
return resultNodes;
}

private flattenChildren(children: T[], level: number, resultNodes: F[], parentMap: boolean[]): void {
children.forEach((child, index) => {
const childParentMap: boolean[] = parentMap.slice();
childParentMap.push(index !== children.length - 1);
this.flattenNode(child, level + 1, resultNodes, childParentMap);
});
}

/**
* Flatten a list of node type T to flattened version of node F.
* Please note that type T may be nested, and the length of `structuredData` may be different
* from that of returned list `F[]`.
*/
flattenNodes(structuredData: T[]): F[] {
const resultNodes: F[] = [];
structuredData.forEach(node => this.flattenNode(node, 0, resultNodes, []));
return resultNodes;
}

/**
* Expand flattened node with current expansion status.
* The returned list may have different length.
*/
expandFlattenedNodes(nodes: F[], treeControl: TreeControl<F, K>): F[] {
const results: F[] = [];
const currentExpand: boolean[] = [];
currentExpand[0] = true;

nodes.forEach(node => {
let expand = true;
for (let i = 0; i <= this.getLevel(node); i++) {
expand = expand && currentExpand[i];
}
if (expand) {
results.push(node);
}
if (this.isExpandable(node)) {
currentExpand[this.getLevel(node) + 1] = treeControl.isExpanded(node);
}
});
return results;
}
}

export class NzTreeFlatDataSource<T, F, K = F> extends DataSource<F> {
_flattenedData = new BehaviorSubject<F[]>([]);

_expandedData = new BehaviorSubject<F[]>([]);

_data: BehaviorSubject<T[]>;

constructor(private _treeControl: FlatTreeControl<F, K>, private _treeFlattener: NzTreeFlattener<T, F, K>, initialData: T[] = []) {
super();
this._data = new BehaviorSubject<T[]>(initialData);
this.flatNodes();
}

setData(value: T[]): void {
this._data.next(value);
this.flatNodes();
}

getData(): T[] {
return this._data.getValue();
}

connect(collectionViewer: CollectionViewer): Observable<F[]> {
const changes = [collectionViewer.viewChange, this._treeControl.expansionModel.changed, this._flattenedData];
return merge(...changes).pipe(
map(() => {
this._expandedData.next(this._treeFlattener.expandFlattenedNodes(this._flattenedData.value, this._treeControl));
return this._expandedData.value;
})
);
}

disconnect(): void {
// no op
}

private flatNodes(): void {
this._flattenedData.next(this._treeFlattener.flattenNodes(this.getData()));
this._treeControl.dataNodes = this._flattenedData.value;
}
}
14 changes: 14 additions & 0 deletions components/tree-view/demo/basic.md
@@ -0,0 +1,14 @@
---
order: 0
title:
zh-CN: 基本
en-US: basic
---

## zh-CN

最简单的用法,选中,禁用,展开等功能。

## en-US

The most basic usage including select, disable and expand features.
98 changes: 98 additions & 0 deletions components/tree-view/demo/basic.ts
@@ -0,0 +1,98 @@
import { SelectionModel } from '@angular/cdk/collections';
import { FlatTreeControl } from '@angular/cdk/tree';
import { Component } from '@angular/core';

import { NzTreeFlatDataSource, NzTreeFlattener } from 'ng-zorro-antd/tree-view';

interface TreeNode {
name: string;
disabled?: boolean;
children?: TreeNode[];
}

const TREE_DATA: TreeNode[] = [
{
name: 'parent 1',
children: [
{
name: 'parent 1-0',
disabled: true,
children: [{ name: 'leaf' }, { name: 'leaf' }]
},
{
name: 'parent 1-1',
children: [{ name: 'leaf' }]
}
]
}
];

interface FlatNode {
expandable: boolean;
name: string;
level: number;
disabled: boolean;
}

@Component({
selector: 'nz-demo-tree-view-basic',
template: `
<nz-tree-view [nzTreeControl]="treeControl" [nzDataSource]="dataSource">
<nz-tree-node *nzTreeNodeDef="let node" nzTreeNodePadding>
<nz-tree-node-toggle nzTreeNodeNoopToggle></nz-tree-node-toggle>
<nz-tree-node-option
[nzDisabled]="node.disabled"
[nzSelected]="selectListSelection.isSelected(node)"
(nzClick)="selectListSelection.toggle(node)"
>
{{ node.name }}
</nz-tree-node-option>
</nz-tree-node>
<nz-tree-node *nzTreeNodeDef="let node; when: hasChild" nzTreeNodePadding>
<nz-tree-node-toggle>
<i nz-icon nzType="caret-down" nzTreeNodeToggleRotateIcon></i>
</nz-tree-node-toggle>
<nz-tree-node-option
[nzDisabled]="node.disabled"
[nzSelected]="selectListSelection.isSelected(node)"
(nzClick)="selectListSelection.toggle(node)"
>
{{ node.name }}
</nz-tree-node-option>
</nz-tree-node>
</nz-tree-view>
`
})
export class NzDemoTreeViewBasicComponent {
private transformer = (node: TreeNode, level: number) => {
return {
expandable: !!node.children && node.children.length > 0,
name: node.name,
level: level,
disabled: !!node.disabled
};
};
selectListSelection = new SelectionModel<FlatNode>(true);

treeControl = new FlatTreeControl<FlatNode>(
node => node.level,
node => node.expandable
);

treeFlattener = new NzTreeFlattener(
this.transformer,
node => node.level,
node => node.expandable,
node => node.children
);

dataSource = new NzTreeFlatDataSource(this.treeControl, this.treeFlattener);

constructor() {
this.dataSource.setData(TREE_DATA);
this.treeControl.expandAll();
}

hasChild = (_: number, node: FlatNode) => node.expandable;
}
14 changes: 14 additions & 0 deletions components/tree-view/demo/checkbox.md
@@ -0,0 +1,14 @@
---
order: 1
title:
zh-CN: 选择框
en-US: checkbox
---

## zh-CN

带选择框的树。

## en-US

Tree with checkboxes.

0 comments on commit 05d58de

Please sign in to comment.