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

atinc / ngx-tethys / 5fa9630c-19a1-4ee7-af3d-6a0c3535952a

08 Oct 2024 08:24AM UTC coverage: 90.438% (+0.007%) from 90.431%
5fa9630c-19a1-4ee7-af3d-6a0c3535952a

push

circleci

minlovehua
refactor: refactor all control-flow directives to new control-flow #TINFR-381

5511 of 6738 branches covered (81.79%)

Branch coverage included in aggregate %.

98 of 104 new or added lines in 58 files covered. (94.23%)

113 existing lines in 17 files now uncovered.

13253 of 14010 relevant lines covered (94.6%)

991.73 hits per line

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

88.0
/src/tree-select/tree-select.component.ts
1
import { EXPANDED_DROPDOWN_POSITIONS, scaleYMotion, TabIndexDisabledControlValueAccessorMixin, ThyClickDispatcher } from 'ngx-tethys/core';
2
import { ThyEmpty } from 'ngx-tethys/empty';
3
import { ThyFlexibleText } from 'ngx-tethys/flexible-text';
4
import { ThyIcon } from 'ngx-tethys/icon';
5
import { ThySelectControl, ThyStopPropagationDirective } from 'ngx-tethys/shared';
6
import { ThyTreeNode } from 'ngx-tethys/tree';
7
import { coerceBooleanProperty, elementMatchClosest, isArray, isObject, produce, warnDeprecation } from 'ngx-tethys/util';
8
import { Observable, of, Subject } from 'rxjs';
9
import { take, takeUntil } from 'rxjs/operators';
10

11
import { CdkConnectedOverlay, CdkOverlayOrigin, ViewportRuler } from '@angular/cdk/overlay';
12
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
13
import { isPlatformBrowser, NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
14
import {
15
    ChangeDetectorRef,
16
    Component,
17
    ContentChild,
×
18
    ElementRef,
2✔
19
    EventEmitter,
17✔
20
    forwardRef,
2✔
21
    HostBinding,
2✔
22
    Inject,
23
    Input,
15!
24
    NgZone,
15✔
25
    OnDestroy,
15✔
26
    OnInit,
2✔
27
    Output,
2✔
28
    PLATFORM_ID,
29
    TemplateRef,
30
    ViewChild
15✔
31
} from '@angular/core';
32
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
4✔
33

2✔
34
import { ThyTreeSelectNode, ThyTreeSelectType } from './tree-select.class';
35

36
type InputSize = 'xs' | 'sm' | 'md' | 'lg' | '';
37

38
export function filterTreeData(treeNodes: ThyTreeSelectNode[], searchText: string, searchKey: string = 'name') {
39
    const filterNodes = (node: ThyTreeSelectNode, result: ThyTreeSelectNode[]) => {
40
        if (node[searchKey] && node[searchKey].indexOf(searchText) !== -1) {
1✔
41
            result.push(node);
42
            return result;
40✔
43
        }
40✔
44
        if (Array.isArray(node.children)) {
40✔
45
            const nodes = node.children.reduce((previous, current) => filterNodes(current, previous), [] as ThyTreeSelectNode[]);
1✔
46
            if (nodes.length) {
1✔
47
                const parentNode = { ...node, children: nodes, expand: true };
1!
48
                result.push(parentNode);
1✔
49
            }
50
        }
51
        return result;
52
    };
53
    const treeData = treeNodes.reduce((previous, current) => filterNodes(current, previous), [] as ThyTreeSelectNode[]);
196✔
54
    return treeData;
55
}
56

196✔
57
/**
58
 * 树选择组件
59
 * @name thy-tree-select
×
60
 * @order 10
×
61
 */
62
@Component({
63
    selector: 'thy-tree-select',
64
    templateUrl: './tree-select.component.html',
65
    providers: [
66
        {
67
            provide: NG_VALUE_ACCESSOR,
68
            useExisting: forwardRef(() => ThyTreeSelect),
69
            multi: true
4✔
70
        }
2✔
71
    ],
72
    standalone: true,
73
    imports: [
2✔
74
        CdkOverlayOrigin,
75
        ThySelectControl,
76
        NgTemplateOutlet,
77
        CdkConnectedOverlay,
4✔
78
        forwardRef(() => ThyTreeSelectNodes),
79
        ThyStopPropagationDirective
×
80
    ],
4✔
81
    host: {
3,724✔
82
        '[attr.tabindex]': 'tabIndex',
111,720✔
83
        '(focus)': 'onFocus($event)',
111,720✔
84
        '(blur)': 'onBlur($event)'
3,720✔
85
    },
86
    animations: [scaleYMotion]
87
})
3,724✔
88
export class ThyTreeSelect extends TabIndexDisabledControlValueAccessorMixin implements OnInit, OnDestroy, ControlValueAccessor {
89
    @HostBinding('class.thy-select-custom') treeSelectClass = true;
3,720✔
90

91
    @HostBinding('class.thy-select') isTreeSelect = true;
92

22✔
93
    // 菜单是否展开
22✔
94
    @HostBinding('class.menu-is-opened') expandTreeSelectOptions = false;
4✔
95

96
    @HostBinding('class.thy-select-custom--multiple') isMulti = false;
22✔
97

98
    public treeNodes: ThyTreeSelectNode[];
99

39✔
100
    public selectedValue: any;
39✔
101

39✔
102
    public selectedNode: ThyTreeSelectNode;
39✔
103

39✔
104
    public selectedNodes: ThyTreeSelectNode[] = [];
39✔
105

39✔
106
    public flattenTreeNodes: ThyTreeSelectNode[] = [];
39✔
107

39✔
108
    virtualTreeNodes: ThyTreeSelectNode[] = [];
39✔
109

39✔
110
    public cdkConnectOverlayWidth = 0;
39✔
111

39✔
112
    public expandedDropdownPositions = EXPANDED_DROPDOWN_POSITIONS;
39✔
113

39✔
114
    public icons: { expand: string; collapse: string; gap?: number } = {
39✔
115
        expand: 'angle-down',
39✔
116
        collapse: 'angle-right',
117
        gap: 15
118
    };
119

120
    private initialled = false;
39✔
121

39✔
122
    private destroy$ = new Subject<void>();
39✔
123

39✔
124
    public valueIsObject = false;
39✔
125

39✔
126
    originTreeNodes: ThyTreeSelectNode[];
39✔
127

39✔
128
    @ContentChild('thyTreeSelectTriggerDisplay')
39✔
129
    thyTreeSelectTriggerDisplayRef: TemplateRef<any>;
39✔
130

39✔
131
    @ContentChild('treeNodeTemplate')
39✔
132
    treeNodeTemplateRef: TemplateRef<any>;
39✔
133

39✔
134
    @ViewChild(CdkOverlayOrigin, { static: true }) cdkOverlayOrigin: CdkOverlayOrigin;
39✔
135

39✔
136
    @ViewChild(CdkConnectedOverlay, { static: true }) cdkConnectedOverlay: CdkConnectedOverlay;
39✔
137

39✔
138
    @ViewChild('customDisplayTemplate', { static: true }) customDisplayTemplate: TemplateRef<any>;
39✔
139

39✔
140
    /**
141
     * treeNodes 数据
142
     * @type ThyTreeSelectNode[]
39✔
143
     */
39✔
144
    @Input()
39✔
145
    set thyTreeNodes(value: ThyTreeSelectNode[]) {
39✔
146
        this.treeNodes = value;
39✔
147
        this.originTreeNodes = value;
4✔
148
        if (this.initialled) {
149
            this.flattenTreeNodes = this.flattenNodes(this.treeNodes, this.flattenTreeNodes, []);
39!
150
            this.setSelectedNodes();
39✔
151

152
            if (this.thyVirtualScroll) {
153
                this.buildFlattenTreeNodes();
154
            }
27✔
155
        }
27✔
156
    }
2✔
157

2✔
158
    /**
2✔
159
     * 开启虚拟滚动
160
     */
161
    @Input({ transform: coerceBooleanProperty }) thyVirtualScroll: boolean = false;
162

163
    /**
39✔
164
     * 树节点的唯一标识
165
     * @type string
166
     */
167
    @Input() thyPrimaryKey = '_id';
1✔
168

169
    /**
170
     * 树节点的显示的字段 key
171
     * @type string
1✔
172
     */
1✔
173
    @Input() thyShowKey = 'name';
174

175
    @Input() thyChildCountKey = 'childCount';
176

177
    /**
2✔
178
     * 单选时,是否显示清除按钮,当为 true 时,显示清除按钮
1✔
179
     * @default false
180
     */
1✔
181
    @Input({ transform: coerceBooleanProperty }) thyAllowClear: boolean;
182

183
    /**
39✔
184
     * 是否多选
185
     * @type boolean
186
     */
196✔
187
    @Input({ transform: coerceBooleanProperty }) thyMultiple = false;
188

189
    /**
1✔
190
     * 是否禁用树选择器,当为 true 禁用树选择器
191
     * @type boolean
192
     */
1✔
193
    @Input({ transform: coerceBooleanProperty }) thyDisable = false;
194

195
    get thyDisabled(): boolean {
196
        return this.thyDisable;
1✔
197
    }
198

199
    /**
200
     * 树选择框默认文字
1✔
201
     * @type string
202
     */
×
203
    @Input() thyPlaceholder = '请选择节点';
4,083✔
204

4,083✔
205
    get placeholder() {
4,083!
206
        return this.thyPlaceholder;
112,044✔
207
    }
112,044✔
208

112,044✔
209
    /**
4,042✔
210
     * 控制树选择的输入框大小
4,042✔
211
     * @type xs | sm | md | default | lg
212
     */
213
    @Input() thySize: InputSize;
4,083✔
214

215
    /**
216
     * 改变空选项的情况下的提示文本
28!
217
     * @type string
218
     */
219
    @Input() thyEmptyOptionsText = '暂时没有数据可选';
62✔
220

221
    /**
4✔
222
     * 设置是否隐藏节点(不可进行任何操作),优先级高于 thyHiddenNodeFn
2!
223
     * @type string
2✔
224
     */
1✔
225
    @Input() thyHiddenNodeKey = 'hidden';
2✔
226

227
    /**
228
     * 设置是否禁用节点(不可进行任何操作),优先级高于 thyDisableNodeFn
229
     * @type string
1✔
230
     */
2✔
231
    @Input() thyDisableNodeKey = 'disabled';
232

233
    /**
234
     * 是否异步加载节点的子节点(显示加载状态),当为 true 时,异步获取
235
     * @type boolean
236
     */
237
    @Input({ transform: coerceBooleanProperty }) thyAsyncNode = false;
2✔
238

1!
239
    /**
1✔
240
     * 是否展示全名
241
     * @type boolean
242
     */
243
    @Input({ transform: coerceBooleanProperty }) thyShowWholeName = false;
1✔
244

245
    /**
246
     * 是否展示搜索
247
     * @type boolean
248
     */
58✔
249
    @Input({ transform: coerceBooleanProperty }) thyShowSearch = false;
58✔
250

251
    /**
252
     * 图标类型,支持 default | especial,已废弃
253
     * @deprecated
14✔
254
     */
1✔
255
    @Input()
256
    set thyIconType(type: ThyTreeSelectType) {
13✔
257
        if (typeof ngDevMode === 'undefined' || ngDevMode) {
13✔
258
            warnDeprecation('This parameter has been deprecation');
13✔
259
        }
260
        // if (type === 'especial') {
261
        //     this.icons = { expand: 'minus-square', collapse: 'plus-square', gap: 20 };
7✔
262
        // } else {
2✔
263
        //     this.icons = { expand: 'caret-right-down', collapse: 'caret-right', gap: 15 };
2✔
264
        // }
2✔
265
    }
266

267
    /**
268
     * 设置是否隐藏节点(不可进行任何操作),优先级低于 thyHiddenNodeKey。
2✔
269
     * @default (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.hidden
2✔
270
     */
2✔
271
    @Input() thyHiddenNodeFn: (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.hidden;
2✔
272

2✔
273
    /**
274
     * 设置是否禁用节点(不可进行任何操作),优先级低于 thyDisableNodeKey。
275
     * @default (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.disabled
13!
UNCOV
276
     */
×
277
    @Input() thyDisableNodeFn: (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.disabled;
278

279
    /**
13✔
280
     * 获取节点的子节点,返回 Observable<ThyTreeSelectNode>。
7✔
281
     * @default (node: ThyTreeSelectNode) => Observable<ThyTreeSelectNode> = (node: ThyTreeSelectNode) => of([])
282
     */
283
    @Input() thyGetNodeChildren: (node: ThyTreeSelectNode) => Observable<ThyTreeSelectNode> = (node: ThyTreeSelectNode) => of([]);
13✔
284

13✔
285
    /**
3✔
286
     * 树选择组件展开和折叠状态事件
287
     */
288
    @Output() thyExpandStatusChange: EventEmitter<boolean> = new EventEmitter<boolean>();
289

1✔
290
    private _getNgModelType() {
291
        if (this.thyMultiple) {
292
            this.valueIsObject = !this.selectedValue[0] || isObject(this.selectedValue[0]);
293
        } else {
3✔
294
            this.valueIsObject = isObject(this.selectedValue);
1✔
295
        }
296
    }
3!
UNCOV
297

×
298
    public buildFlattenTreeNodes() {
299
        this.virtualTreeNodes = this.getFlattenTreeNodes(this.treeNodes);
3!
300
    }
3✔
301

3✔
302
    private getFlattenTreeNodes(rootTrees: ThyTreeSelectNode[] = this.treeNodes) {
303
        const forEachTree = (tree: ThyTreeSelectNode[], fn: any, result: ThyTreeSelectNode[] = []) => {
3✔
304
            tree.forEach(item => {
305
                result.push(item);
306
                if (item.children && fn(item)) {
307
                    forEachTree(item.children, fn, result);
12✔
308
                }
3✔
309
            });
3✔
310
            return result;
3✔
311
        };
3✔
312
        return forEachTree(rootTrees, (node: ThyTreeSelectNode) => !!node.expand);
313
    }
314

9✔
315
    writeValue(value: any): void {
2✔
316
        this.selectedValue = value;
317

2✔
318
        if (value) {
319
            this._getNgModelType();
320
        }
7✔
321
        this.setSelectedNodes();
7✔
322
    }
323

324
    constructor(
325
        public elementRef: ElementRef,
326
        private ngZone: NgZone,
1✔
327
        private ref: ChangeDetectorRef,
1!
328
        @Inject(PLATFORM_ID) private platformId: string,
1✔
329
        private thyClickDispatcher: ThyClickDispatcher,
1✔
330
        private viewportRuler: ViewportRuler
1✔
331
    ) {
2✔
332
        super();
18✔
333
    }
334

335
    ngOnInit() {
1✔
336
        this.isMulti = this.thyMultiple;
1✔
337
        this.flattenTreeNodes = this.flattenNodes(this.treeNodes, this.flattenTreeNodes, []);
338
        this.setSelectedNodes();
1✔
339
        this.initialled = true;
340

341
        if (this.thyVirtualScroll) {
1✔
342
            this.buildFlattenTreeNodes();
343
        }
344

345
        if (isPlatformBrowser(this.platformId)) {
346
            this.thyClickDispatcher
347
                .clicked(0)
348
                .pipe(takeUntil(this.destroy$))
349
                .subscribe(event => {
1✔
350
                    event.stopPropagation();
351
                    if (!this.elementRef.nativeElement.contains(event.target) && this.expandTreeSelectOptions) {
352
                        this.ngZone.run(() => {
353
                            this.close();
354
                            this.ref.markForCheck();
355
                        });
356
                    }
357
                });
358
        }
359
        this.viewportRuler
360
            .change()
361
            .pipe(takeUntil(this.destroy$))
362
            .subscribe(() => {
363
                this.init();
364
            });
365
    }
366

367
    onFocus($event: FocusEvent) {
368
        const inputElement: HTMLInputElement = this.elementRef.nativeElement.querySelector('input');
369
        inputElement?.focus();
370
    }
371

372
    onBlur($event: FocusEvent) {
373
        // 1. Tab 聚焦后自动聚焦到 input 输入框,此分支下直接返回,无需触发 onTouchedFn
374
        // 2. 打开选择框后如果点击弹框内导致 input 失焦,无需触发 onTouchedFn
375
        if (elementMatchClosest($event?.relatedTarget as HTMLElement, ['thy-tree-select', 'thy-tree-select-nodes'])) {
376
            return;
377
        }
378
        this.onTouchedFn();
379
    }
380

381
    ngOnDestroy(): void {
382
        this.destroy$.next();
1✔
383
    }
384

385
    get selectedValueObject() {
386
        return this.thyMultiple ? this.selectedNodes : this.selectedNode;
387
    }
388

389
    searchValue(searchText: string) {
9✔
390
        this.treeNodes = filterTreeData(this.originTreeNodes, searchText.trim(), this.thyShowKey);
391
    }
392

393
    public setPosition() {
394
        this.ngZone.onStable
395
            .asObservable()
396
            .pipe(take(1))
397
            .subscribe(() => {
398
                this.cdkConnectedOverlay.overlayRef.updatePosition();
399
            });
113✔
400
    }
401

402
    private init() {
403
        this.cdkConnectOverlayWidth = this.cdkOverlayOrigin.elementRef.nativeElement.getBoundingClientRect().width;
404
    }
405

406
    private flattenNodes(
407
        nodes: ThyTreeSelectNode[] = [],
408
        resultNodes: ThyTreeSelectNode[] = [],
409
        parentPrimaryValue: string[] = []
410
    ): ThyTreeSelectNode[] {
411
        resultNodes = resultNodes.concat(nodes);
412
        let nodesLeafs: ThyTreeSelectNode[] = [];
413
        (nodes || []).forEach(item => {
414
            item.parentValues = parentPrimaryValue;
415
            item.level = item.parentValues.length;
1✔
416
            if (item.children && isArray(item.children)) {
417
                const nodeLeafs = this.flattenNodes(item.children, resultNodes, [...parentPrimaryValue, item[this.thyPrimaryKey]]);
418
                nodesLeafs = [...nodesLeafs, ...nodeLeafs];
419
            }
1✔
420
        });
421
        return [...nodes, ...nodesLeafs];
13✔
422
    }
423

13✔
424
    private _findTreeNode(value: string): ThyTreeSelectNode {
13✔
425
        return (this.flattenTreeNodes || []).find(item => item[this.thyPrimaryKey] === value);
13!
426
    }
427

428
    private setSelectedNodes() {
13✔
429
        if (this.selectedValue) {
13✔
430
            // 多选数据初始化
13✔
431
            if (this.thyMultiple) {
13✔
432
                if (this.selectedValue.length > 0) {
13✔
433
                    if (this.valueIsObject && Object.keys(this.selectedValue[0]).indexOf(this.thyPrimaryKey) >= 0) {
13✔
434
                        this.selectedNodes = this.selectedValue.map((item: any) => {
13✔
435
                            return this._findTreeNode(item[this.thyPrimaryKey]);
13✔
436
                        });
13✔
437
                    } else {
13✔
438
                        this.selectedNodes = this.selectedValue.map((item: any) => {
13✔
439
                            return this._findTreeNode(item);
13✔
440
                        });
13✔
441
                    }
442
                }
443
            } else {
13✔
444
                // 单选数据初始化
445
                if (this.valueIsObject) {
446
                    if (Object.keys(this.selectedValue).indexOf(this.thyPrimaryKey) >= 0) {
361✔
447
                        this.selectedNode = this._findTreeNode(this.selectedValue[this.thyPrimaryKey]);
244!
448
                    }
50✔
449
                } else {
450
                    this.selectedNode = this._findTreeNode(this.selectedValue);
451
                }
452
            }
117!
453
        } else {
454
            this.selectedNodes = [];
455
            this.selectedNode = null;
456
        }
418✔
457
    }
394✔
458

459
    openSelectPop() {
24!
460
        if (this.thyDisable) {
24✔
461
            return;
462
        }
×
463
        this.cdkConnectOverlayWidth = this.cdkOverlayOrigin.elementRef.nativeElement.getBoundingClientRect().width;
464
        this.expandTreeSelectOptions = !this.expandTreeSelectOptions;
465
        this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
373✔
466
    }
363✔
467

468
    close() {
10!
469
        if (this.expandTreeSelectOptions) {
10✔
470
            this.expandTreeSelectOptions = false;
471
            this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
×
472
            this.onTouchedFn();
473
        }
474
    }
707✔
475

707✔
476
    clearSelectedValue(event: Event) {
536!
477
        event.stopPropagation();
110✔
478
        this.selectedValue = null;
479
        this.selectedNode = null;
480
        this.selectedNodes = [];
481
        this.onChangeFn(this.selectedValue);
171!
482
    }
483

484
    private _changeSelectValue() {
485
        if (this.valueIsObject) {
707✔
486
            this.selectedValue = this.thyMultiple ? this.selectedNodes : this.selectedNode;
707✔
487
        } else {
707✔
488
            this.selectedValue = this.thyMultiple
489
                ? this.selectedNodes.map(item => item[this.thyPrimaryKey])
490
                : this.selectedNode[this.thyPrimaryKey];
1✔
491
        }
492
        this.onChangeFn(this.selectedValue);
493
        if (!this.thyMultiple) {
12!
494
            this.onTouchedFn();
12✔
495
        }
496
    }
497

498
    removeMultipleSelectedNode(event: { item: ThyTreeSelectNode; $eventOrigin: Event }) {
1✔
499
        this.removeSelectedNode(event.item, event.$eventOrigin);
1!
500
    }
1✔
501

502
    // thyMultiple = true 时,移除数据时调用
503
    removeSelectedNode(node: ThyTreeSelectNode, event?: Event) {
×
504
        if (event) {
×
505
            event.stopPropagation();
506
        }
507
        if (this.thyDisable) {
×
508
            return;
509
        }
510
        if (this.thyMultiple) {
1!
511
            this.selectedNodes = produce(this.selectedNodes).remove((item: ThyTreeSelectNode) => {
1✔
512
                return item[this.thyPrimaryKey] === node[this.thyPrimaryKey];
1✔
513
            });
514
            this._changeSelectValue();
515
        }
516
    }
1!
517

×
518
    selectNode(node: ThyTreeSelectNode) {
519
        if (!this.thyMultiple) {
520
            this.selectedNode = node;
521
            this.expandTreeSelectOptions = false;
24✔
522
            this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
523
            this._changeSelectValue();
1✔
524
        } else {
525
            if (
526
                this.selectedNodes.find(item => {
1✔
527
                    return item[this.thyPrimaryKey] === node[this.thyPrimaryKey];
528
                })
529
            ) {
530
                this.removeSelectedNode(node);
531
            } else {
532
                this.selectedNodes = produce(this.selectedNodes).add(node);
1✔
533
                this._changeSelectValue();
534
            }
535
        }
536
    }
537

538
    getNodeChildren(node: ThyTreeSelectNode) {
539
        const result = this.thyGetNodeChildren(node);
540
        if (result && result.subscribe) {
541
            result.pipe().subscribe((data: ThyTreeSelectNode[]) => {
542
                const nodes = this.flattenNodes(data, this.flattenTreeNodes, [...node.parentValues, node[this.thyPrimaryKey]]);
543
                const otherNodes = nodes.filter((item: ThyTreeNode) => {
544
                    return !this.flattenTreeNodes.find(hasItem => {
545
                        return hasItem[this.thyPrimaryKey] === item[this.thyPrimaryKey];
546
                    });
547
                });
548
                this.flattenTreeNodes = [...this.flattenTreeNodes, ...otherNodes];
549
                node.children = data;
550
            });
551
            return result;
552
        }
553
    }
554
}
555

556
const DEFAULT_ITEM_SIZE = 40;
557

558
/**
559
 * @private
560
 */
561
@Component({
562
    selector: 'thy-tree-select-nodes',
563
    templateUrl: './tree-select-nodes.component.html',
564
    standalone: true,
565
    imports: [
566
        NgTemplateOutlet,
567
        CdkVirtualScrollViewport,
568
        CdkFixedSizeVirtualScroll,
569
        CdkVirtualForOf,
570
        ThyEmpty,
571
        NgClass,
572
        NgStyle,
573
        ThyIcon,
574
        ThyFlexibleText
575
    ],
576
    host: {
577
        '[attr.tabindex]': '-1'
578
    }
579
})
580
export class ThyTreeSelectNodes implements OnInit {
581
    @HostBinding('class') class: string;
582

583
    nodeList: ThyTreeSelectNode[] = [];
584

585
    @Input() set treeNodes(value: ThyTreeSelectNode[]) {
586
        const treeSelectHeight = this.defaultItemSize * value.length;
587
        // 父级设置了max-height:300 & padding:10 0; 故此处最多设置280,否则将出现滚动条
588
        this.thyVirtualHeight = treeSelectHeight > 300 ? '280px' : `${treeSelectHeight}px`;
589
        this.nodeList = value;
590
        this.hasNodeChildren = this.nodeList.every(
591
            item => !item.hasOwnProperty('children') || (!item?.children?.length && !item?.childCount)
592
        );
593
    }
594

595
    @Input() thyVirtualScroll: boolean = false;
596

597
    public primaryKey = this.parent.thyPrimaryKey;
598

599
    public showKey = this.parent.thyShowKey;
600

601
    public isMultiple = this.parent.thyMultiple;
602

603
    public valueIsObject = this.parent.valueIsObject;
604

605
    public selectedValue = this.parent.selectedValue;
606

607
    public childCountKey = this.parent.thyChildCountKey;
608

609
    public treeNodeTemplateRef = this.parent.treeNodeTemplateRef;
610

611
    public defaultItemSize = DEFAULT_ITEM_SIZE;
612

613
    public thyVirtualHeight: string = null;
614

615
    public hasNodeChildren: boolean = false;
616

617
    constructor(public parent: ThyTreeSelect) {}
618

619
    ngOnInit() {
620
        this.class = this.isMultiple ? 'thy-tree-select-dropdown thy-tree-select-dropdown-multiple' : 'thy-tree-select-dropdown';
621
    }
622

623
    treeNodeIsSelected(node: ThyTreeSelectNode) {
624
        if (this.parent.thyMultiple) {
625
            return (this.parent.selectedNodes || []).find(item => {
626
                return item[this.primaryKey] === node[this.primaryKey];
627
            });
628
        } else {
629
            return this.parent.selectedNode && this.parent.selectedNode[this.primaryKey] === node[this.primaryKey];
630
        }
631
    }
632

633
    treeNodeIsHidden(node: ThyTreeSelectNode) {
634
        if (this.parent.thyHiddenNodeKey) {
635
            return node[this.parent.thyHiddenNodeKey];
636
        }
637
        if (this.parent.thyHiddenNodeFn) {
638
            return this.parent.thyHiddenNodeFn(node);
639
        }
640
        return false;
641
    }
642

643
    treeNodeIsDisable(node: ThyTreeSelectNode) {
644
        if (this.parent.thyDisableNodeKey) {
645
            return node[this.parent.thyDisableNodeKey];
646
        }
647
        if (this.parent.thyDisableNodeFn) {
648
            return this.parent.thyDisableNodeFn(node);
649
        }
650
        return false;
651
    }
652

653
    treeNodeIsExpand(node: ThyTreeSelectNode) {
654
        let isSelectedNodeParent = false;
655
        if (this.parent.thyMultiple) {
656
            isSelectedNodeParent = !!(this.parent.selectedNodes || []).find(item => {
657
                return item.parentValues.indexOf(node[this.primaryKey]) > -1;
658
            });
659
        } else {
660
            isSelectedNodeParent = this.parent.selectedNode
661
                ? this.parent.selectedNode.parentValues.indexOf(node[this.primaryKey]) > -1
662
                : false;
663
        }
664
        const isExpand = node.expand || (Object.keys(node).indexOf('expand') < 0 && isSelectedNodeParent);
665
        node.expand = isExpand;
666
        return isExpand;
667
    }
668

669
    getNodeChildren(node: ThyTreeSelectNode) {
670
        return this.parent.getNodeChildren(node);
671
    }
672

673
    selectTreeNode(event: Event, node: ThyTreeSelectNode) {
674
        if (!this.treeNodeIsDisable(node)) {
675
            this.parent.selectNode(node);
676
        }
677
    }
678

679
    nodeExpandToggle(event: Event, node: ThyTreeSelectNode) {
680
        event.stopPropagation();
681
        if (Object.keys(node).indexOf('expand') > -1) {
682
            node.expand = !node.expand;
683
        } else {
684
            if (this.treeNodeIsExpand(node)) {
685
                node.expand = false;
686
            } else {
687
                node.expand = true;
688
            }
689
        }
690

691
        if (node.expand && this.parent.thyAsyncNode) {
692
            this.getNodeChildren(node).subscribe(() => {
693
                this.parent.setPosition();
694
            });
695
        }
696
        // this.parent.setPosition();
697
        if (this.thyVirtualScroll) {
698
            this.parent.buildFlattenTreeNodes();
699
        }
700
    }
701

702
    tabTrackBy(index: number, item: ThyTreeSelectNode) {
703
        return index;
704
    }
705
}
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