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

atinc / ngx-tethys / d9ae709b-3c27-4b69-b125-b8b80b54f90b

pending completion
d9ae709b-3c27-4b69-b125-b8b80b54f90b

Pull #2757

circleci

mengshuicmq
fix: fix code review
Pull Request #2757: feat(color-picker): color-picker support disabled (#INFR-8645)

98 of 6315 branches covered (1.55%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

2392 of 13661 relevant lines covered (17.51%)

83.12 hits per line

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

9.68
/src/tree/tree-node.component.ts
1
import { ThyDragStartEvent, ThyDragContentDirective } from 'ngx-tethys/drag-drop';
2
import { fromEvent, Subject } from 'rxjs';
3
import { filter, takeUntil } from 'rxjs/operators';
4

5
import { normalizePassiveListenerOptions } from '@angular/cdk/platform';
6
import {
7
    ChangeDetectorRef,
8
    Component,
9
    ContentChild,
10
    ElementRef,
11
    EventEmitter,
12
    HostBinding,
13
    Inject,
14
    Input,
15
    NgZone,
16
    OnChanges,
1✔
17
    OnDestroy,
18
    OnInit,
19
    Output,
20
    SimpleChanges,
21
    TemplateRef,
22
    ViewChild,
1✔
23
    ViewEncapsulation
24
} from '@angular/core';
×
25

26
import { THY_TREE_ABSTRACT_TOKEN, ThyTreeAbstractComponent } from './tree-abstract';
27
import { ThyTreeNode } from './tree-node.class';
×
28
import { ThyTreeEmitEvent, ThyTreeNodeCheckState, ThyClickBehavior } from './tree.class';
29
import { ThyTreeService } from './tree.service';
30
import { InputBoolean, InputNumber } from 'ngx-tethys/core';
×
31
import { ThyLoadingComponent } from 'ngx-tethys/loading';
×
32
import { ThyIconComponent } from 'ngx-tethys/icon';
×
33
import { NgIf, NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
×
34

×
35
const passiveEventListenerOptions = <AddEventListenerOptions>normalizePassiveListenerOptions({ passive: true });
×
36

×
37
/**
×
38
 * 树形控件的节点组件
×
39
 * @private
×
40
 * @name thy-tree-node
×
41
 */
×
42
@Component({
×
43
    selector: 'thy-tree-node',
×
44
    templateUrl: './tree-node.component.html',
×
45
    encapsulation: ViewEncapsulation.None,
×
46
    standalone: true,
×
47
    imports: [ThyDragContentDirective, NgIf, ThyIconComponent, NgClass, NgStyle, NgTemplateOutlet, ThyLoadingComponent]
48
})
×
49
export class ThyTreeNodeComponent implements OnDestroy, OnInit, OnChanges {
50
    /**
×
51
     * node 节点展现所需的数据
52
     */
53
    @Input() node: ThyTreeNode;
54

×
55
    /**
×
56
     * 设置 TreeNode 是否支持异步加载
×
57
     */
×
58
    @Input() @InputBoolean() thyAsync = false;
59

60
    /**
61
     * 设置 TreeNode 是否支持多选节点
×
62
     */
×
63
    @Input() @InputBoolean() thyMultiple = false;
64

65
    /**
×
66
     * 设置 TreeNode 是否支持拖拽排序
×
67
     */
68
    @Input() @InputBoolean() thyDraggable = false;
69

×
70
    /**
×
71
     * 设置 TreeNode 是否支持 Checkbox 选择
72
     */
73
    @Input() @InputBoolean() thyCheckable = false;
×
74

75
    /**
76
     * 点击节点的行为,`default` 为选中当前节点,`selectCheckbox` 为选中节点的 Checkbox, `thyCheckable` 为 true 时生效。
77
     * @default default
×
78
     */
79
    @Input() thyClickBehavior: ThyClickBehavior;
80

81
    /**
82
     * 设置节点名称是否支持超出截取
83
     * @default false
84
     */
×
85
    @Input() @InputBoolean() thyTitleTruncate: boolean;
86

87
    /**
88
     * 设置 TreeNode 的渲染模板
89
     */
90
    @Input() templateRef: TemplateRef<any>;
91

×
92
    /**
×
93
     * 设置子的空数据渲染模板
×
94
     */
95
    @Input() emptyChildrenTemplateRef: TemplateRef<any>;
×
96

×
97
    /**
98
     * 设置 node 点击事件
×
99
     */
×
100
    @Output() thyOnClick: EventEmitter<ThyTreeEmitEvent> = new EventEmitter<ThyTreeEmitEvent>();
×
101

×
102
    /**
×
103
     * 双击 node 事件
104
     */
105
    @Output() thyDblClick: EventEmitter<ThyTreeEmitEvent> = new EventEmitter<ThyTreeEmitEvent>();
×
106

107
    /**
108
     * 点击展开触发事件
×
109
     */
110
    @Output() thyOnExpandChange: EventEmitter<ThyTreeEmitEvent> = new EventEmitter<ThyTreeEmitEvent>();
111

112
    /**
113
     * 设置 check 选择事件
114
     */
115
    @Output() thyOnCheckboxChange: EventEmitter<ThyTreeEmitEvent> = new EventEmitter<ThyTreeEmitEvent>();
×
116

×
117
    /**
×
118
     * 设置 childrenTree 的渲染模板
×
119
     */
120
    @ContentChild('childrenTree') childrenTreeTemplateRef: TemplateRef<any>;
121

122
    /** The native `<div class="thy-tree-node-wrapper thy-sortable-item"></div>` element. */
123
    @ViewChild('treeNodeWrapper', { static: true }) treeNodeWrapper: ElementRef<HTMLElement>;
×
124

×
125
    @HostBinding('class.thy-tree-node') thyTreeNodeClass = true;
126

127
    @HostBinding('class') itemClass: string;
128

129
    /**
×
130
     * 开启虚拟滚动时,单行节点的高度,当`thySize`为`default`时,该参数才生效
131
     */
132
    @Input() @InputNumber() thyItemSize = 44;
×
133

×
134
    /**
×
135
     * 设置节点缩进距离,缩进距离 = thyIndent * node.level
136
     */
137
    @Input() @InputNumber() thyIndent = 25;
×
138

×
139
    public get nodeIcon() {
140
        return this.node.origin.icon;
×
141
    }
×
142

143
    public get nodeIconStyle() {
144
        return this.node.origin.iconStyle;
×
145
    }
×
146

×
147
    private destroy$ = new Subject<void>();
148

149
    checkState = ThyTreeNodeCheckState;
150

151
    constructor(
152
        @Inject(THY_TREE_ABSTRACT_TOKEN) public root: ThyTreeAbstractComponent,
×
153
        public thyTreeService: ThyTreeService,
154
        private ngZone: NgZone,
155
        cdr: ChangeDetectorRef
×
156
    ) {
157
        this.thyTreeService
158
            .statusChanged()
×
159
            .pipe(
×
160
                filter(data => data.node.key === this.node.key),
161
                takeUntil(this.destroy$)
162
            )
×
163
            .subscribe(() => {
164
                this.thyTreeService.syncFlattenTreeNodes();
165
            });
166
    }
167

168
    private changeDragIconVisibility(event: Event, showDragIcon: boolean): void {
×
169
        const nodeElement = event.target as HTMLElement;
×
170
        const dragIcon: HTMLElement | null = nodeElement.querySelector<HTMLElement>('.thy-tree-drag-icon');
171
        if (dragIcon) {
172
            dragIcon.style.visibility = showDragIcon ? 'visible' : 'hidden';
173
        }
×
174
    }
×
175

176
    public clickNode(event: Event) {
1✔
177
        if (this.node.isDisabled) {
178
            this.expandNode(event);
179
        } else {
180
            if (this.thyCheckable && this.thyClickBehavior === 'selectCheckbox') {
181
                this.clickNodeCheck(event);
182
            } else {
1✔
183
                if (this.root.thyMultiple) {
184
                    this.root.toggleTreeNode(this.node);
185
                } else {
186
                    this.root.selectTreeNode(this.node);
187
                }
188
            }
189
        }
190
        this.thyOnClick.emit({
191
            eventName: 'click',
192
            event: event,
193
            node: this.node
194
        });
195
    }
196

197
    public dbClickNode(event: Event) {
198
        this.thyDblClick.emit({
199
            eventName: 'dbclick',
200
            event: event,
201
            node: this.node
202
        });
203
    }
204

1✔
205
    public clickNodeCheck(event: Event) {
206
        event.stopPropagation();
207
        if (this.node.isChecked === ThyTreeNodeCheckState.unchecked) {
208
            this.node.setChecked(true);
1✔
209
        } else if (this.node.isChecked === ThyTreeNodeCheckState.checked) {
210
            this.node.setChecked(false);
211
        } else if (this.node.isChecked === ThyTreeNodeCheckState.indeterminate) {
212
            if (this.node.children?.length) {
1✔
213
                const activeChildren = this.node.children.filter(item => !item.isDisabled);
214
                const isAllActiveChildrenChecked = activeChildren.every(item => item.isChecked);
215
                this.node.setChecked(!isAllActiveChildrenChecked);
216
            } else {
1✔
217
                this.node.setChecked(true);
218
            }
219
        }
220
        this.thyOnCheckboxChange.emit({
1✔
221
            eventName: 'checkboxChange',
222
            event: event,
223
            node: this.node
224
        });
1✔
225
    }
226

227
    public expandNode(event: Event) {
228
        event.stopPropagation();
1✔
229
        this.node.setExpanded(!this.node.isExpanded);
230
        if (this.root.thyShowExpand) {
231
            this.thyOnExpandChange.emit({
232
                eventName: 'expand',
1✔
233
                event: event,
234
                node: this.node
235
            });
236
            if (this.thyAsync && this.node.children.length === 0) {
237
                this.node.setLoading(true);
238
            }
239
        }
240
    }
241

242
    public isShowExpand(node: ThyTreeNode) {
243
        return this.root.isShowExpand(node);
244
    }
245

246
    ngOnInit(): void {
247
        this.itemClass = this.node?.itemClass?.join(' ');
248
        this.ngZone.runOutsideAngular(() => {
249
            fromEvent(this.treeNodeWrapper.nativeElement, 'mouseenter', passiveEventListenerOptions)
250
                .pipe(takeUntil(this.destroy$))
251
                .subscribe((event: MouseEvent) => {
252
                    if (!this.root.thyDraggable) {
253
                        return;
254
                    } else if (this.root.thyDraggable && !this.root.thyBeforeDragStart) {
255
                        this.changeDragIconVisibility(event, true);
256
                    } else {
257
                        const parentNode = this.node.getParentNode();
258
                        const containerItems = parentNode?.getChildren() ?? this.root.treeNodes;
259
                        const dragStartEvent: ThyDragStartEvent = {
260
                            event: event as DragEvent,
261
                            item: this.node,
262
                            containerItems,
263
                            currentIndex: containerItems.indexOf(this.node)
264
                        };
265
                        this.changeDragIconVisibility(event, this.root.thyBeforeDragStart(dragStartEvent));
266
                    }
267
                });
268

269
            fromEvent(this.treeNodeWrapper.nativeElement, 'mouseleave', passiveEventListenerOptions)
270
                .pipe(takeUntil(this.destroy$))
271
                .subscribe((event: MouseEvent) => {
272
                    if (!this.root.thyDraggable) {
273
                        return;
274
                    } else {
275
                        this.changeDragIconVisibility(event, false);
276
                    }
277
                });
278
        });
279
    }
280

281
    ngOnChanges(changes: SimpleChanges): void {
282
        if (changes.node && !changes.node.isFirstChange()) {
283
            this.itemClass = changes?.node?.currentValue.itemClass?.join(' ');
284
        }
285
    }
286

287
    ngOnDestroy(): void {
288
        this.destroy$.next();
289
        this.destroy$.complete();
290
    }
291
}
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

© 2025 Coveralls, Inc