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

atinc / ngx-tethys / 9be4f007-ba18-4b6b-87e5-d72093b20564

21 Aug 2024 10:34AM UTC coverage: 90.447% (-0.02%) from 90.465%
9be4f007-ba18-4b6b-87e5-d72093b20564

push

circleci

web-flow
feat(color-picker): hex column does not show alpha value #TINFR-62 (#3158)

5504 of 6730 branches covered (81.78%)

Branch coverage included in aggregate %.

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

10 existing lines in 3 files now uncovered.

13253 of 14008 relevant lines covered (94.61%)

995.59 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, NgFor, NgIf, 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
        NgIf,
77
        NgTemplateOutlet,
4✔
78
        CdkConnectedOverlay,
79
        forwardRef(() => ThyTreeSelectNodes),
×
80
        ThyStopPropagationDirective
4✔
81
    ],
3,724✔
82
    host: {
111,720✔
83
        '[attr.tabindex]': 'tabIndex',
111,720✔
84
        '(focus)': 'onFocus($event)',
3,720✔
85
        '(blur)': 'onBlur($event)'
86
    },
87
    animations: [scaleYMotion]
3,724✔
88
})
89
export class ThyTreeSelect extends TabIndexDisabledControlValueAccessorMixin implements OnInit, OnDestroy, ControlValueAccessor {
3,720✔
90
    @HostBinding('class.thy-select-custom') treeSelectClass = true;
91

92
    @HostBinding('class.thy-select') isTreeSelect = true;
22✔
93

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

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

99
    public treeNodes: ThyTreeSelectNode[];
39✔
100

39✔
101
    public selectedValue: any;
39✔
102

39✔
103
    public selectedNode: ThyTreeSelectNode;
39✔
104

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

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

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

39✔
111
    public cdkConnectOverlayWidth = 0;
39✔
112

39✔
113
    public expandedDropdownPositions = EXPANDED_DROPDOWN_POSITIONS;
39✔
114

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

39✔
121
    private initialled = false;
39✔
122

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

39✔
125
    public valueIsObject = false;
39✔
126

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

557
const DEFAULT_ITEM_SIZE = 40;
558

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

586
    nodeList: ThyTreeSelectNode[] = [];
587

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

598
    @Input() thyVirtualScroll: boolean = false;
599

600
    public primaryKey = this.parent.thyPrimaryKey;
601

602
    public showKey = this.parent.thyShowKey;
603

604
    public isMultiple = this.parent.thyMultiple;
605

606
    public valueIsObject = this.parent.valueIsObject;
607

608
    public selectedValue = this.parent.selectedValue;
609

610
    public childCountKey = this.parent.thyChildCountKey;
611

612
    public treeNodeTemplateRef = this.parent.treeNodeTemplateRef;
613

614
    public defaultItemSize = DEFAULT_ITEM_SIZE;
615

616
    public thyVirtualHeight: string = null;
617

618
    public hasNodeChildren: boolean = false;
619

620
    constructor(public parent: ThyTreeSelect) {}
621

622
    ngOnInit() {
623
        this.class = this.isMultiple ? 'thy-tree-select-dropdown thy-tree-select-dropdown-multiple' : 'thy-tree-select-dropdown';
624
    }
625

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

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

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

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

672
    getNodeChildren(node: ThyTreeSelectNode) {
673
        return this.parent.getNodeChildren(node);
674
    }
675

676
    selectTreeNode(event: Event, node: ThyTreeSelectNode) {
677
        if (!this.treeNodeIsDisable(node)) {
678
            this.parent.selectNode(node);
679
        }
680
    }
681

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

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

705
    tabTrackBy(index: number, item: ThyTreeSelectNode) {
706
        return index;
707
    }
708
}
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