• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / #41

14 Jul 2025 08:03AM UTC coverage: 90.295% (+0.002%) from 90.293%
#41

push

web-flow
ci: update environment to env

5521 of 6794 branches covered (81.26%)

Branch coverage included in aggregate %.

13747 of 14545 relevant lines covered (94.51%)

903.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.9
/src/tree/tree-node.component.ts
1
import {
2
    Component,
3
    ElementRef,
4
    TemplateRef,
5
    ViewEncapsulation,
6
    numberAttribute,
7
    inject,
8
    input,
9
    computed,
10
    contentChild,
11
    viewChild,
12
    output
13
} from '@angular/core';
14

15
import { THY_TREE_ABSTRACT_TOKEN } from './tree-abstract';
16
import { ThyTreeNode } from './tree.class';
17
import { ThyTreeEmitEvent, ThyTreeNodeCheckState, ThyClickBehavior } from './tree.class';
1✔
18
import { ThyTreeService } from './tree.service';
19
import { ThyLoading } from 'ngx-tethys/loading';
657✔
20
import { ThyIcon } from 'ngx-tethys/icon';
657✔
21
import { NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
657✔
22
import { coerceBooleanProperty } from 'ngx-tethys/util';
657✔
23

657✔
24
/**
657✔
25
 * 树形控件的节点组件
657✔
26
 * @private
657✔
27
 * @name thy-tree-node
657✔
28
 */
657✔
29
@Component({
657✔
30
    selector: 'thy-tree-node',
657✔
31
    templateUrl: './tree-node.component.html',
657✔
32
    encapsulation: ViewEncapsulation.None,
657✔
33
    imports: [ThyIcon, NgClass, NgStyle, NgTemplateOutlet, ThyLoading],
657✔
34
    host: {
657✔
35
        '[class.thy-tree-node]': 'true',
657✔
36
        '[class]': 'itemClass()'
657✔
37
    }
657✔
38
})
657✔
39
export class ThyTreeNodeComponent {
141✔
40
    root = inject(THY_TREE_ABSTRACT_TOKEN);
41
    thyTreeService = inject(ThyTreeService);
657✔
42

×
43
    /**
44
     * node 节点展现所需的数据
657✔
45
     */
739✔
46
    readonly node = input<ThyTreeNode>(null);
47

657✔
48
    /**
49
     * 设置 TreeNode 是否支持异步加载
50
     */
7✔
51
    readonly thyAsync = input(false, { transform: coerceBooleanProperty });
7✔
52

2✔
53
    /**
54
     * 设置 TreeNode 是否支持多选节点
55
     */
5✔
56
    readonly thyMultiple = input(false, { transform: coerceBooleanProperty });
2✔
57

58
    /**
59
     * 设置 TreeNode 是否支持拖拽排序
3✔
60
     */
1✔
61
    readonly thyDraggable = input(false, { transform: coerceBooleanProperty });
62

63
    /**
2✔
64
     * 设置 TreeNode 是否支持 Checkbox 选择
65
     */
66
    readonly thyCheckable = input(false, { transform: coerceBooleanProperty });
67

7✔
68
    /**
69
     * 点击节点的行为,`default` 为选中当前节点,`selectCheckbox` 为选中节点的 Checkbox, `thyCheckable` 为 true 时生效。
70
     * @default default
71
     */
72
    readonly thyClickBehavior = input<ThyClickBehavior>(undefined);
73

74
    /**
1✔
75
     * 设置节点名称是否支持超出截取
76
     * @default false
77
     */
78
    readonly thyTitleTruncate = input(false, { transform: coerceBooleanProperty });
79

80
    /**
81
     * 设置 TreeNode 的渲染模板
9✔
82
     */
9✔
83
    readonly templateRef = input<TemplateRef<any>>();
9✔
84

5✔
85
    /**
86
     * 设置子的空数据渲染模板
4✔
87
     */
1✔
88
    readonly emptyChildrenTemplateRef = input<TemplateRef<any>>();
89

3!
90
    /**
3!
91
     * 设置 node 点击事件
16✔
92
     */
5✔
93
    readonly thyOnClick = output<ThyTreeEmitEvent>();
3✔
94

95
    /**
96
     * 双击 node 事件
×
97
     */
98
    readonly thyDblClick = output<ThyTreeEmitEvent>();
99

9✔
100
    /**
101
     * 点击展开触发事件
102
     */
103
    readonly thyOnExpandChange = output<ThyTreeEmitEvent>();
104

105
    /**
106
     * 设置 check 选择事件
6✔
107
     */
6✔
108
    readonly thyOnCheckboxChange = output<ThyTreeEmitEvent>();
6✔
109

6!
110
    /**
6✔
111
     * 设置 childrenTree 的渲染模板
112
     */
113
    readonly childrenTreeTemplateRef = contentChild<TemplateRef<any>>('childrenTree');
114

115
    /** The native `<div class="thy-tree-node-wrapper thy-sortable-item"></div>` element. */
6✔
116
    readonly treeNodeWrapper = viewChild<ElementRef<HTMLElement>>('treeNodeWrapper');
1✔
117

118
    /**
119
     * 开启虚拟滚动时,单行节点的高度,当`thySize`为`default`时,该参数才生效
120
     */
121
    readonly thyItemSize = input(44, { transform: numberAttribute });
1,122✔
122

123
    /**
1✔
124
     * 设置节点缩进距离,缩进距离 = thyIndent * node.level
125
     */
126
    readonly thyIndent = input(25, { transform: numberAttribute });
127

128
    readonly nodeIcon = computed(() => {
129
        return this.node().origin.icon;
130
    });
131

132
    readonly nodeIconStyle = computed(() => {
133
        return this.node().origin.iconStyle;
134
    });
135

136
    protected readonly itemClass = computed(() => {
137
        return this.node()?.itemClass?.join(' ');
138
    });
139

140
    checkState = ThyTreeNodeCheckState;
141

142
    public clickNode(event: Event) {
143
        const node = this.node();
1✔
144
        if (node.isDisabled) {
145
            this.expandNode(event);
146
        } else {
147
            if (this.thyCheckable() && this.thyClickBehavior() === 'selectCheckbox') {
148
                this.clickNodeCheck(event);
149
            } else {
150
                if (this.root.thyMultiple()) {
151
                    this.root.toggleTreeNode(node);
152
                } else {
153
                    this.root.selectTreeNode(node);
154
                }
155
            }
156
        }
157
        this.thyOnClick.emit({
158
            eventName: 'click',
159
            event: event,
160
            node: node
161
        });
162
    }
163

164
    public dbClickNode(event: Event) {
165
        this.thyDblClick.emit({
166
            eventName: 'dbclick',
167
            event: event,
168
            node: this.node()
169
        });
170
    }
171

172
    public clickNodeCheck(event: Event) {
173
        event.stopPropagation();
174
        const node = this.node();
175
        if (node.isChecked === ThyTreeNodeCheckState.unchecked) {
176
            node.setChecked(true);
177
        } else if (node.isChecked === ThyTreeNodeCheckState.checked) {
178
            node.setChecked(false);
179
        } else if (node.isChecked === ThyTreeNodeCheckState.indeterminate) {
180
            if (node.children?.length) {
181
                const activeChildren = node.children.filter(item => !item.isDisabled);
182
                const isAllActiveChildrenChecked = activeChildren.every(item => item.isChecked);
183
                node.setChecked(!isAllActiveChildrenChecked);
184
            } else {
185
                node.setChecked(true);
186
            }
187
        }
188
        this.thyOnCheckboxChange.emit({
189
            eventName: 'checkboxChange',
190
            event: event,
191
            node: node
192
        });
193
    }
194

195
    public expandNode(event: Event) {
196
        event.stopPropagation();
197
        const node = this.node();
198
        this.node().setExpanded(!node.isExpanded);
199
        if (this.root.thyShowExpand()) {
200
            this.thyOnExpandChange.emit({
201
                eventName: 'expand',
202
                event: event,
203
                node: node
204
            });
205
            if (this.thyAsync() && node.children.length === 0) {
206
                node.setLoading(true);
207
            }
208
        }
209
    }
210

211
    public isShowExpand(node: ThyTreeNode) {
212
        return this.root.isShowExpand(node);
213
    }
214
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc