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

atinc / ngx-tethys / 881c8997-29c3-4d01-9ef1-22092f16cec2

03 Apr 2024 03:31AM UTC coverage: 90.404% (-0.2%) from 90.585%
881c8997-29c3-4d01-9ef1-22092f16cec2

Pull #3062

circleci

minlovehua
refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()
Pull Request #3062: refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()

5411 of 6635 branches covered (81.55%)

Branch coverage included in aggregate %.

217 of 223 new or added lines in 82 files covered. (97.31%)

201 existing lines in 53 files now uncovered.

13176 of 13925 relevant lines covered (94.62%)

980.1 hits per line

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

87.47
/src/tree-select/tree-select.component.ts
1
import {
2
    TabIndexDisabledControlValueAccessorMixin,
3
    getFlexiblePositions,
4
    ThyClickDispatcher,
5
    EXPANDED_DROPDOWN_POSITIONS
6
} from 'ngx-tethys/core';
7
import { ThyTreeNode } from 'ngx-tethys/tree';
8
import { elementMatchClosest, isArray, isObject, produce, warnDeprecation } from 'ngx-tethys/util';
9
import { Observable, of, Subject } from 'rxjs';
10
import { take, takeUntil } from 'rxjs/operators';
11

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

2✔
35
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
36
import { ThyEmpty } from 'ngx-tethys/empty';
37
import { ThyIcon } from 'ngx-tethys/icon';
38
import { ThyFlexibleText } from 'ngx-tethys/flexible-text';
39
import { ThySelectControl, ThyStopPropagationDirective } from 'ngx-tethys/shared';
40
import { ThyTreeSelectNode, ThyTreeSelectType } from './tree-select.class';
41
import { scaleYMotion } from 'ngx-tethys/core';
1✔
42

43
type InputSize = 'xs' | 'sm' | 'md' | 'lg' | '';
38✔
44

38✔
45
export function filterTreeData(treeNodes: ThyTreeSelectNode[], searchText: string, searchKey: string = 'name') {
38!
46
    const filterNodes = (node: ThyTreeSelectNode, result: ThyTreeSelectNode[]) => {
×
47
        if (node[searchKey] && node[searchKey].indexOf(searchText) !== -1) {
×
48
            result.push(node);
49
            return result;
50
        }
51
        if (Array.isArray(node.children)) {
188✔
52
            const nodes = node.children.reduce((previous, current) => filterNodes(current, previous), [] as ThyTreeSelectNode[]);
53
            if (nodes.length) {
54
                const parentNode = { ...node, children: nodes, expand: true };
188✔
55
                result.push(parentNode);
56
            }
57
        }
×
58
        return result;
×
59
    };
60
    const treeData = treeNodes.reduce((previous, current) => filterNodes(current, previous), [] as ThyTreeSelectNode[]);
61
    return treeData;
62
}
63

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

38✔
99
    @HostBinding('class.thy-select') isTreeSelect = true;
38✔
100

38✔
101
    // 菜单是否展开
38✔
102
    @HostBinding('class.menu-is-opened') expandTreeSelectOptions = false;
38✔
103

38✔
104
    @HostBinding('class.thy-select-custom--multiple') isMulti = false;
38✔
105

38✔
106
    public treeNodes: ThyTreeSelectNode[];
38✔
107

38✔
108
    public selectedValue: any;
38✔
109

38✔
110
    public selectedNode: ThyTreeSelectNode;
38✔
111

38✔
112
    public selectedNodes: ThyTreeSelectNode[] = [];
38✔
113

38✔
114
    public flattenTreeNodes: ThyTreeSelectNode[] = [];
115

116
    virtualTreeNodes: ThyTreeSelectNode[] = [];
117

118
    public cdkConnectOverlayWidth = 0;
38✔
119

38✔
120
    public positions: ConnectionPositionPair[];
38✔
121

38✔
122
    public expandedDropdownPositions = EXPANDED_DROPDOWN_POSITIONS;
38✔
123

38✔
124
    public icons: { expand: string; collapse: string; gap?: number } = {
38✔
125
        expand: 'angle-down',
38✔
126
        collapse: 'angle-right',
38✔
127
        gap: 15
38✔
128
    };
38✔
129

38✔
130
    private initialled = false;
38✔
131

38✔
132
    private destroy$ = new Subject<void>();
38✔
133

38✔
134
    public valueIsObject = false;
38✔
135

38✔
136
    originTreeNodes: ThyTreeSelectNode[];
38✔
137

38✔
138
    @ContentChild('thyTreeSelectTriggerDisplay')
139
    thyTreeSelectTriggerDisplayRef: TemplateRef<any>;
140

38✔
141
    @ContentChild('treeNodeTemplate')
38✔
142
    treeNodeTemplateRef: TemplateRef<any>;
38✔
143

38✔
144
    @ViewChild(CdkOverlayOrigin, { static: true }) cdkOverlayOrigin: CdkOverlayOrigin;
38✔
145

38✔
146
    @ViewChild(CdkConnectedOverlay, { static: true }) cdkConnectedOverlay: CdkConnectedOverlay;
3✔
147

148
    @ViewChild('customDisplayTemplate', { static: true }) customDisplayTemplate: TemplateRef<any>;
38!
149

38✔
150
    /**
151
     * treeNodes 数据
152
     * @type ThyTreeSelectNode[]
153
     */
27✔
154
    @Input()
27✔
155
    set thyTreeNodes(value: ThyTreeSelectNode[]) {
2✔
156
        this.treeNodes = value;
2✔
157
        this.originTreeNodes = value;
2✔
158
        if (this.initialled) {
159
            this.flattenTreeNodes = this.flattenNodes(this.treeNodes, this.flattenTreeNodes, []);
160
            this.setSelectedNodes();
161
        }
162
    }
38✔
163

164
    /**
165
     * 开启虚拟滚动
166
     */
1✔
167
    @Input({ transform: booleanAttribute }) thyVirtualScroll: boolean = false;
168

169
    /**
170
     * 树节点的唯一标识
1✔
171
     * @type string
1✔
172
     */
173
    @Input() thyPrimaryKey = '_id';
174

175
    /**
176
     * 树节点的显示的字段 key
2✔
177
     * @type string
1✔
178
     */
179
    @Input() thyShowKey = 'name';
1✔
180

181
    @Input() thyChildCountKey = 'childCount';
182

38✔
183
    /**
184
     * 单选时,是否显示清除按钮,当为 true 时,显示清除按钮
185
     * @default false
188✔
186
     */
187
    @Input({ transform: booleanAttribute }) thyAllowClear: boolean;
188

1✔
189
    /**
190
     * 是否多选
191
     * @type boolean
1✔
192
     */
193
    @Input({ transform: booleanAttribute }) thyMultiple = false;
194

195
    /**
1✔
196
     * 是否禁用树选择器,当为 true 禁用树选择器
197
     * @type boolean
198
     */
199
    @Input({ transform: booleanAttribute }) thyDisable = false;
1✔
200

201
    get thyDisabled(): boolean {
×
202
        return this.thyDisable;
3,146✔
203
    }
3,146✔
204

3,146!
205
    /**
84,107✔
206
     * 树选择框默认文字
84,107✔
207
     * @type string
84,107✔
208
     */
3,107✔
209
    @Input() thyPlaceholder = '请选择节点';
3,107✔
210

211
    get placeholder() {
212
        return this.thyPlaceholder;
3,146✔
213
    }
214

215
    /**
28!
216
     * 控制树选择的输入框大小
217
     * @type xs | sm | md | default | lg
218
     */
58✔
219
    @Input() thySize: InputSize;
220

4✔
221
    /**
2!
222
     * 改变空选项的情况下的提示文本
2✔
223
     * @type string
1✔
224
     */
2✔
225
    @Input() thyEmptyOptionsText = '暂时没有数据可选';
226

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

233
    /**
234
     * 设置是否禁用节点(不可进行任何操作),优先级高于 thyDisableNodeFn
235
     * @type string
236
     */
2✔
237
    @Input() thyDisableNodeKey = 'disabled';
1!
238

1✔
239
    /**
240
     * 是否异步加载节点的子节点(显示加载状态),当为 true 时,异步获取
241
     * @type boolean
242
     */
1✔
243
    @Input({ transform: booleanAttribute }) thyAsyncNode = false;
244

245
    /**
246
     * 是否展示全名
247
     * @type boolean
54✔
248
     */
54✔
249
    @Input({ transform: booleanAttribute }) thyShowWholeName = false;
250

251
    /**
252
     * 是否展示搜索
14✔
253
     * @type boolean
1✔
254
     */
255
    @Input({ transform: booleanAttribute }) thyShowSearch = false;
13✔
256

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

273
    /**
274
     * 设置是否隐藏节点(不可进行任何操作),优先级低于 thyHiddenNodeKey。
13!
275
     * @default (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.hidden
×
276
     */
277
    @Input() thyHiddenNodeFn: (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.hidden;
278

13✔
279
    /**
7✔
280
     * 设置是否禁用节点(不可进行任何操作),优先级低于 thyDisableNodeKey。
281
     * @default (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.disabled
282
     */
13✔
283
    @Input() thyDisableNodeFn: (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.disabled;
13✔
284

3✔
285
    /**
286
     * 获取节点的子节点,返回 Observable<ThyTreeSelectNode>。
287
     * @default (node: ThyTreeSelectNode) => Observable<ThyTreeSelectNode> = (node: ThyTreeSelectNode) => of([])
288
     */
1✔
289
    @Input() thyGetNodeChildren: (node: ThyTreeSelectNode) => Observable<ThyTreeSelectNode> = (node: ThyTreeSelectNode) => of([]);
290

291
    /**
292
     * 树选择组件展开和折叠状态事件
3✔
293
     */
1✔
294
    @Output() thyExpandStatusChange: EventEmitter<boolean> = new EventEmitter<boolean>();
295

3!
296
    private _getNgModelType() {
×
297
        if (this.thyMultiple) {
298
            this.valueIsObject = !this.selectedValue[0] || isObject(this.selectedValue[0]);
3!
299
        } else {
3✔
300
            this.valueIsObject = isObject(this.selectedValue);
3✔
301
        }
302
    }
3✔
303

304
    public buildFlattenTreeNodes() {
305
        this.virtualTreeNodes = this.getFlattenTreeNodes(this.treeNodes);
306
    }
12✔
307

3✔
308
    private getFlattenTreeNodes(rootTrees: ThyTreeSelectNode[] = this.treeNodes) {
3✔
309
        const forEachTree = (tree: ThyTreeSelectNode[], fn: any, result: ThyTreeSelectNode[] = []) => {
3✔
310
            tree.forEach(item => {
3✔
311
                result.push(item);
312
                if (item.children && fn(item)) {
313
                    forEachTree(item.children, fn, result);
9✔
314
                }
2✔
315
            });
316
            return result;
2✔
317
        };
318
        return forEachTree(rootTrees, (node: ThyTreeSelectNode) => !!node.expand);
319
    }
7✔
320

7✔
321
    writeValue(value: any): void {
322
        this.selectedValue = value;
323

324
        if (value) {
325
            this._getNgModelType();
1✔
326
        }
1!
327
        this.setSelectedNodes();
1✔
328
    }
1✔
329

1✔
330
    constructor(
2✔
331
        public elementRef: ElementRef,
18✔
332
        private ngZone: NgZone,
333
        private ref: ChangeDetectorRef,
334
        @Inject(PLATFORM_ID) private platformId: string,
1✔
335
        private thyClickDispatcher: ThyClickDispatcher,
1✔
336
        private viewportRuler: ViewportRuler
337
    ) {
1✔
338
        super();
339
    }
340

1✔
341
    ngOnInit() {
342
        this.positions = getFlexiblePositions('bottom', 4);
343
        this.isMulti = this.thyMultiple;
344
        this.flattenTreeNodes = this.flattenNodes(this.treeNodes, this.flattenTreeNodes, []);
345
        this.setSelectedNodes();
346
        this.initialled = true;
347

348
        if (this.thyVirtualScroll) {
1✔
349
            this.buildFlattenTreeNodes();
350
        }
351

352
        if (isPlatformBrowser(this.platformId)) {
353
            this.thyClickDispatcher
354
                .clicked(0)
355
                .pipe(takeUntil(this.destroy$))
356
                .subscribe(event => {
357
                    event.stopPropagation();
358
                    if (!this.elementRef.nativeElement.contains(event.target) && this.expandTreeSelectOptions) {
359
                        this.ngZone.run(() => {
360
                            this.close();
361
                            this.ref.markForCheck();
362
                        });
363
                    }
364
                });
365
        }
366
        this.viewportRuler
367
            .change()
368
            .pipe(takeUntil(this.destroy$))
369
            .subscribe(() => {
370
                this.init();
371
            });
372
    }
373

374
    onFocus($event: FocusEvent) {
375
        const inputElement: HTMLInputElement = this.elementRef.nativeElement.querySelector('input');
376
        inputElement?.focus();
377
    }
378

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

388
    ngOnDestroy(): void {
8✔
389
        this.destroy$.next();
390
    }
391

392
    get selectedValueObject() {
393
        return this.thyMultiple ? this.selectedNodes : this.selectedNode;
394
    }
395

396
    searchValue(searchText: string) {
397
        this.treeNodes = filterTreeData(this.originTreeNodes, searchText.trim(), this.thyShowKey);
398
    }
399

109✔
400
    public setPosition() {
401
        this.ngZone.onStable
402
            .asObservable()
403
            .pipe(take(1))
404
            .subscribe(() => {
405
                this.cdkConnectedOverlay.overlayRef.updatePosition();
406
            });
407
    }
408

409
    private init() {
410
        this.cdkConnectOverlayWidth = this.cdkOverlayOrigin.elementRef.nativeElement.getBoundingClientRect().width;
411
    }
412

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

13✔
431
    private _findTreeNode(value: string): ThyTreeSelectNode {
13✔
432
        return (this.flattenTreeNodes || []).find(item => item[this.thyPrimaryKey] === value);
13✔
433
    }
13✔
434

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

466
    openSelectPop() {
10!
467
        if (this.thyDisable) {
10✔
468
            return;
UNCOV
469
        }
×
470
        this.cdkConnectOverlayWidth = this.cdkOverlayOrigin.elementRef.nativeElement.getBoundingClientRect().width;
471
        this.expandTreeSelectOptions = !this.expandTreeSelectOptions;
472
        this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
707✔
473
    }
707✔
474

536!
475
    close() {
110✔
476
        if (this.expandTreeSelectOptions) {
477
            this.expandTreeSelectOptions = false;
478
            this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
479
            this.onTouchedFn();
171!
480
        }
481
    }
482

483
    clearSelectedValue(event: Event) {
707✔
484
        event.stopPropagation();
707✔
485
        this.selectedValue = null;
707✔
486
        this.selectedNode = null;
487
        this.selectedNodes = [];
488
        this.onChangeFn(this.selectedValue);
1✔
489
    }
490

491
    private _changeSelectValue() {
12!
492
        if (this.valueIsObject) {
12✔
493
            this.selectedValue = this.thyMultiple ? this.selectedNodes : this.selectedNode;
494
        } else {
495
            this.selectedValue = this.thyMultiple
496
                ? this.selectedNodes.map(item => item[this.thyPrimaryKey])
1✔
497
                : this.selectedNode[this.thyPrimaryKey];
1!
498
        }
1✔
499
        this.onChangeFn(this.selectedValue);
500
        if (!this.thyMultiple) {
UNCOV
501
            this.onTouchedFn();
×
UNCOV
502
        }
×
503
    }
504

UNCOV
505
    removeMultipleSelectedNode(event: { item: ThyTreeSelectNode; $eventOrigin: Event }) {
×
506
        this.removeSelectedNode(event.item, event.$eventOrigin);
507
    }
508

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

1✔
525
    selectNode(node: ThyTreeSelectNode) {
526
        if (!this.thyMultiple) {
527
            this.selectedNode = node;
528
            this.expandTreeSelectOptions = false;
529
            this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
530
            this._changeSelectValue();
1✔
531
        } else {
532
            if (
533
                this.selectedNodes.find(item => {
534
                    return item[this.thyPrimaryKey] === node[this.thyPrimaryKey];
535
                })
536
            ) {
537
                this.removeSelectedNode(node);
538
            } else {
539
                this.selectedNodes = produce(this.selectedNodes).add(node);
540
                this._changeSelectValue();
541
            }
542
        }
543
    }
544

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

563
const DEFAULT_ITEM_SIZE = 40;
564

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

592
    nodeList: ThyTreeSelectNode[] = [];
593

594
    @Input() set treeNodes(value: ThyTreeSelectNode[]) {
595
        const treeSelectHeight = this.defaultItemSize * value.length;
596
        // 父级设置了max-height:300 & padding:10 0; 故此处最多设置280,否则将出现滚动条
597
        this.thyVirtualHeight = treeSelectHeight > 300 ? '280px' : `${treeSelectHeight}px`;
598
        this.nodeList = value;
599
    }
600

601
    @Input() thyVirtualScroll: boolean = false;
602

603
    public primaryKey = this.parent.thyPrimaryKey;
604

605
    public showKey = this.parent.thyShowKey;
606

607
    public isMultiple = this.parent.thyMultiple;
608

609
    public valueIsObject = this.parent.valueIsObject;
610

611
    public selectedValue = this.parent.selectedValue;
612

613
    public childCountKey = this.parent.thyChildCountKey;
614

615
    public treeNodeTemplateRef = this.parent.treeNodeTemplateRef;
616

617
    public defaultItemSize = DEFAULT_ITEM_SIZE;
618

619
    public thyVirtualHeight: string = null;
620

621
    constructor(public parent: ThyTreeSelect) {}
622

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

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

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

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

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

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

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

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

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

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