+
+
+
+
+
+ {{ item[displayField] }}
+
+
+
+
diff --git a/src/app/components/tree-root/tree-root.component.scss b/src/app/components/tree-root/tree-root.component.scss
new file mode 100644
index 0000000..0f5fd05
--- /dev/null
+++ b/src/app/components/tree-root/tree-root.component.scss
@@ -0,0 +1,38 @@
+.container {
+ display: flex;
+ flex-direction: column;
+}
+
+.nested-level {
+ margin-left: 20px;
+}
+
+.item {
+ padding: 10px;
+ border-bottom: 1px solid #cccccc;
+ border-left: 1px solid #cccccc;
+ user-select: none;
+
+ i {
+ padding-right: 10px;
+
+ &.handle {
+ color: #888888;
+ }
+ }
+
+ &:hover, &.activated {
+ background-color: #f0f0f0;
+ cursor: pointer;
+ }
+}
+
+.container.dark {
+ .item {
+ border-color: #555555;
+
+ &:hover, &.activated {
+ background-color: #333333;
+ }
+ }
+}
diff --git a/src/app/components/tree-root/tree-root.component.ts b/src/app/components/tree-root/tree-root.component.ts
new file mode 100644
index 0000000..172dc0f
--- /dev/null
+++ b/src/app/components/tree-root/tree-root.component.ts
@@ -0,0 +1,253 @@
+import {Component, EventEmitter, Input, OnInit, Output, ViewChildren} from '@angular/core';
+import {debug} from '../../utility';
+
+/**
+ * A recursive tree listing component.
+ */
+@Component({
+ selector: 'app-tree-root',
+ templateUrl: './tree-root.component.html',
+ styleUrls: ['./tree-root.component.scss'],
+})
+export class TreeRootComponent implements OnInit {
+ /** The immediate children of this component. */
+ @ViewChildren('childComponents')
+ protected childComponents;
+
+ /** How deeply nested this component is in the tree. */
+ @Input()
+ public nestingLevel = 0;
+
+ /** Field on the listing record with the display string. */
+ @Input()
+ public displayField = 'name';
+
+ /** Field on the listing record that contains the children of an item. */
+ @Input()
+ public childrenField = 'children';
+
+ /** Optionally, field on the listing record that contains the