Skip to content

Commit

Permalink
ngx-tree - Virtual Scrolling (#952)
Browse files Browse the repository at this point in the history
* Virtual scrolling

* Styles update

* Virtual scrolling v2

* Virtual scrolling v3

* Virtual scrolling v4

* Virtual scrolling v5

* Virtual scrolling v6

* Virtual scrolling v7
  • Loading branch information
AlvaroMCSL authored Dec 27, 2022
1 parent 6357e9b commit f9da4fa
Show file tree
Hide file tree
Showing 13 changed files with 687 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<ng-template *ngIf="template" [ngTemplateOutlet]="template" [ngTemplateOutletContext]="data"> </ng-template>
<ng-content *ngIf="expanded"></ng-content>
<ngx-tree
*ngIf="children?.length && expandable && expanded"
*ngIf="!virtualScrolling && children?.length && expandable && expanded"
class="ngx-sub-tree"
[nodes]="children"
[template]="template"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class TreeNodeComponent implements OnChanges {
collapse: 'icon-tree-collapse',
expand: 'icon-tree-expand'
};
@Input() virtualScrolling = false;

@Output() activate = new EventEmitter();
@Output() deactivate = new EventEmitter();
Expand All @@ -43,6 +44,7 @@ export class TreeNodeComponent implements OnChanges {
@Output() collapse = new EventEmitter();

data: any;
depth = 0;

ngOnChanges(): void {
this.data = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface TreeNode {
id?: number;
label: string;
children?: TreeNode[];
model: {
Expand All @@ -8,4 +9,9 @@ export interface TreeNode {
expandable?: boolean;
expanded?: boolean;
selectable?: boolean;
depth?: number;
display?: boolean;
childNodesCount?: number;
parentId?: number;
index?: number;
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,63 @@
<div class="ngx-tree" [class.one-leaf]="hasOneLeaf">
<ul class="vertical-list">
<ngx-tree-node
*ngFor="let node of nodes"
[node]="node"
[disabled]="node.disabled"
[expandable]="node.expandable"
[expanded]="node.expanded"
[selectable]="node.selectable"
[icons]="icons"
[label]="node.label"
[model]="node.model"
[children]="node.children"
[template]="template"
(expand)="expand.emit($event)"
(collapse)="collapse.emit($event)"
(activate)="activate.emit($event)"
(deactivate)="deactivate.emit($event)"
(selectNode)="selectNode.emit($event)"
<div class="ngx-tree" [class.one-leaf]="!virtualScrolling && hasOneLeaf">
<ul class="vertical-list">
<cdk-virtual-scroll-viewport
*ngIf="treeStructure && virtualScrolling"
[itemSize]="nodeHeight"
[minBufferPx]="nodeHeight * filteredTree.length > maxVirtualScrollHeight ? maxVirtualScrollHeight * 2 : (nodeHeight * filteredTree.length) * 2"
[maxBufferPx]="nodeHeight * filteredTree.length > maxVirtualScrollHeight ? maxVirtualScrollHeight * 4 : (nodeHeight * filteredTree.length) * 4"
class="virtual-container"
[style.height]="nodeHeight * filteredTree.length > maxVirtualScrollHeight ? maxVirtualScrollHeight + 'px' : (nodeHeight * filteredTree.length) + 'px'"
>
</ngx-tree-node>
<ng-content *ngIf="!nodes"></ng-content>
<ng-container *cdkVirtualFor="let node of filteredTree; let i = index; let c = count; trackBy: trackBy;">
<div class="node-container" [style.height]="nodeHeight + 'px'" >
<div
[class]="i === c - 1 && c > 1 ? 'depth-indicator_filled' : 'depth-indicator'"
[style.width]="i + 1 !== c && node.depth - filteredTree[i + 1].depth > 1 ? (node.depth - (node.depth - filteredTree[i + 1].depth)) * depthPadding + 'px' : ((node.depth - 1) * depthPadding) + 'px'"
[style.height]="nodeHeight + 'px'"
></div>
<div
*ngIf="i + 1 !== c && node.depth - filteredTree[i + 1].depth > 1"
class="depth-indicator_filled"
[style.width]="(node.depth - filteredTree[i + 1].depth - 1) * depthPadding + 'px'"
></div>
<div
[class.empty]="empty | memoize: node:filteredTree"
[class.filled]="filled | memoize: node:filteredTree"
[class.filled-single]="filled | memoize: node:filteredTree:true"
[class.dots]="dots | memoize: node:filteredTree"
>
<ng-container [ngTemplateOutlet]="ngxTreeNode" [ngTemplateOutletContext]="{ node }"></ng-container>
</div>
</div>
</ng-container>
</cdk-virtual-scroll-viewport>
<ng-container *ngIf="nodes?.length && !nodeElms?.length && !virtualScrolling">
<ng-container *ngFor="let node of nodes">
<ng-container [ngTemplateOutlet]="ngxTreeNode" [ngTemplateOutletContext]="{ node }"></ng-container>
</ng-container>
</ng-container>
<ng-content *ngIf="!nodes?.length && nodeElms?.length && !virtualScrolling"></ng-content>
</ul>
<div class="ngx-tree-vr" *ngIf="nodes?.length || nodeElms?.length"></div>
<div class="ngx-tree-vr" *ngIf="(nodes?.length || nodeElms?.length) && !virtualScrolling"></div>
</div>
<ng-template #ngxTreeNode let-node="node">
<ngx-tree-node
[node]="node"
[disabled]="node.disabled"
[expandable]="node.expandable"
[expanded]="node.expanded"
[selectable]="node.selectable"
[icons]="icons"
[label]="node.label"
[model]="node.model"
[children]="node.children"
[template]="template"
[virtualScrolling]="virtualScrolling"
(expand)="onExpand($event)"
(collapse)="onCollapse($event)"
(activate)="activate.emit($event)"
(deactivate)="deactivate.emit($event)"
(selectNode)="selectNode.emit($event)"
>
</ngx-tree-node>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
$color-tree-line: #667080;
$color-tree-text: #b3b6bd;

$thickness: 1px;
$gap: 28px;
$dashes: 5.3px;

.ngx-tree {
position: relative;

Expand Down Expand Up @@ -87,4 +91,86 @@ $color-tree-text: #b3b6bd;
left: -3px;
}
}

.virtual-container {
width: 100%;

.cdk-virtual-scroll-content-wrapper {
width: max-content;
}
}

.node-container {
display: flex;
}

.depth-indicator {
background-position-y: -5px;

&_filled {
height: 13px !important;
background: radial-gradient(at 5.5px 30%, $color-tree-line, 2.5px, transparent 0) 0/28px 32px,
conic-gradient(at 1px 50%, #0000 75%, $color-tree-line 0) 5px 0/28px 5.1px;
background-position-y: 1.1px;
}
}

.empty,
.filled,
.filled-single {
position: relative;
}

.empty::before,
.filled::after,
.filled-single::after {
content: ' ';
position: absolute;
width: 5px;
height: 5px;
left: 3px;
top: 8px;
border-radius: 5px;
}

.empty::before {
background: none;
border: 1px solid $color-tree-line;
}

.depth-indicator,
.empty::after,
.filled::before,
.dots {
background: conic-gradient(at $thickness 50%, #0000 75%, $color-tree-line 0) 5px 0 / ($gap $dashes);
}

.empty::after,
.filled::before {
content: ' ';
position: absolute;
width: 10px;
background-repeat: repeat-y;
background-position-x: 5px;
}

.empty::after {
height: calc(100% - 5px);
top: 15px;
}

.filled::before {
height: 10px;
top: 1px;
}

.filled::after,
.filled-single::after {
background: $color-tree-line;
}

.dots {
background-repeat: repeat-y;
background-position-x: 5px;
}
}
Loading

0 comments on commit f9da4fa

Please sign in to comment.