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

atinc / ngx-tethys / 680f7a28-9ac6-472a-8247-d3892246c266

pending completion
680f7a28-9ac6-472a-8247-d3892246c266

push

circleci

minlovehua
textarea 调用草稿

5104 of 6321 branches covered (80.75%)

Branch coverage included in aggregate %.

12917 of 13697 relevant lines covered (94.31%)

971.88 hits per line

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

87.7
/src/tree-select/tree-select.component.ts
1
import {
2
    AbstractControlValueAccessor,
3
    Constructor,
4
    getFlexiblePositions,
5
    InputBoolean,
6
    mixinDisabled,
7
    mixinTabIndex,
8
    ThyCanDisable,
9
    ThyClickDispatcher,
10
    ThyHasTabIndex
11
} from 'ngx-tethys/core';
12
import { ThyTreeNode } from 'ngx-tethys/tree';
13
import { elementMatchClosest, isArray, isObject, produce, warnDeprecation } from 'ngx-tethys/util';
14
import { Observable, of, Subject } from 'rxjs';
15
import { take, takeUntil } from 'rxjs/operators';
16

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

39
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
40
import { ThyEmptyComponent } from 'ngx-tethys/empty';
41
import { ThyIconComponent } from 'ngx-tethys/icon';
1✔
42
import { ThySelectControlComponent, ThyStopPropagationDirective } from 'ngx-tethys/shared';
43
import { ThyTreeSelectNode, ThyTreeSelectType } from './tree-select.class';
38✔
44

38✔
45
type InputSize = 'xs' | 'sm' | 'md' | 'lg' | '';
38!
46

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

66
const _MixinBase: Constructor<ThyHasTabIndex> & Constructor<ThyCanDisable> & typeof AbstractControlValueAccessor = mixinTabIndex(
67
    mixinDisabled(AbstractControlValueAccessor)
4✔
68
);
2✔
69

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

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

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

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

38✔
111
    public treeNodes: ThyTreeSelectNode[];
38✔
112

38✔
113
    public selectedValue: any;
114

115
    public selectedNode: ThyTreeSelectNode;
116

117
    public selectedNodes: ThyTreeSelectNode[] = [];
38✔
118

38✔
119
    public flattenTreeNodes: ThyTreeSelectNode[] = [];
38✔
120

38✔
121
    virtualTreeNodes: ThyTreeSelectNode[] = [];
38✔
122

38✔
123
    public cdkConnectOverlayWidth = 0;
38✔
124

38✔
125
    public positions: ConnectionPositionPair[];
38✔
126

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

38✔
133
    private initialled = false;
38✔
134

38✔
135
    private destroy$ = new Subject<void>();
38✔
136

38✔
137
    public valueIsObject = false;
138

139
    originTreeNodes: ThyTreeSelectNode[];
38✔
140

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

38✔
144
    @ContentChild('treeNodeTemplate')
38✔
145
    treeNodeTemplateRef: TemplateRef<any>;
3✔
146

147
    @ViewChild(CdkOverlayOrigin, { static: true }) cdkOverlayOrigin: CdkOverlayOrigin;
38!
148

38✔
149
    @ViewChild(CdkConnectedOverlay, { static: true }) cdkConnectedOverlay: CdkConnectedOverlay;
150

151
    @ViewChild('customDisplayTemplate', { static: true }) customDisplayTemplate: TemplateRef<any>;
152

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

167
    /**
168
     * 开启虚拟滚动
169
     */
1✔
170
    @Input() @InputBoolean() thyVirtualScroll: boolean = false;
1✔
171

172
    /**
173
     * 树节点的唯一标识
174
     * @type string
175
     */
2✔
176
    @Input() thyPrimaryKey = '_id';
1✔
177

178
    /**
1✔
179
     * 树节点的显示的字段 key
180
     * @type string
181
     */
38✔
182
    @Input() thyShowKey = 'name';
183

184
    @Input() thyChildCountKey = 'childCount';
188✔
185

186
    /**
187
     * 单选时,是否显示清除按钮,当为 true 时,显示清除按钮
1✔
188
     * @default false
189
     */
190
    @Input() @InputBoolean() thyAllowClear: boolean;
1✔
191

192
    /**
193
     * 是否多选
194
     * @type boolean
1✔
195
     */
196
    @Input() @InputBoolean() thyMultiple = false;
197

198
    /**
1✔
199
     * 是否禁用树选择器,当为 true 禁用树选择器
200
     * @type boolean
×
201
     */
3,146✔
202
    @Input() @InputBoolean() thyDisable = false;
3,146✔
203

3,146!
204
    get thyDisabled(): boolean {
84,107✔
205
        return this.thyDisable;
84,107✔
206
    }
84,107✔
207

3,107✔
208
    /**
3,107✔
209
     * 树选择框默认文字
210
     * @type string
211
     */
3,146✔
212
    @Input() thyPlaceholder = '请选择节点';
213

214
    get placeholder() {
28!
215
        return this.thyPlaceholder;
216
    }
217

58✔
218
    /**
219
     * 控制树选择的输入框大小
4✔
220
     * @type xs | sm | md | default | lg
2!
221
     */
2✔
222
    @Input() thySize: InputSize;
1✔
223

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

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

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

1✔
242
    /**
243
     * 是否异步加载节点的子节点(显示加载状态),当为 true 时,异步获取
244
     * @type boolean
245
     */
246
    @Input() @InputBoolean() thyAsyncNode = false;
54✔
247

54✔
248
    /**
249
     * 是否展示全名
250
     * @type boolean
251
     */
14✔
252
    @Input() @InputBoolean() thyShowWholeName = false;
1✔
253

254
    /**
13✔
255
     * 是否展示搜索
13✔
256
     * @type boolean
13✔
257
     */
258
    @Input() @InputBoolean() thyShowSearch = false;
259

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

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

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

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

294
    /**
3!
295
     * 树选择组件展开和折叠状态事件
×
296
     */
297
    @Output() thyExpandStatusChange: EventEmitter<boolean> = new EventEmitter<boolean>();
3!
298

3✔
299
    private _getNgModelType() {
3✔
300
        if (this.thyMultiple) {
301
            this.valueIsObject = !this.selectedValue[0] || isObject(this.selectedValue[0]);
3✔
302
        } else {
303
            this.valueIsObject = isObject(this.selectedValue);
304
        }
305
    }
12✔
306

3✔
307
    public buildFlattenTreeNodes() {
3✔
308
        this.virtualTreeNodes = this.getFlattenTreeNodes(this.treeNodes);
3✔
309
    }
3✔
310

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

324
    writeValue(value: any): void {
1✔
325
        this.selectedValue = value;
1!
326

1✔
327
        if (value) {
1✔
328
            this._getNgModelType();
1✔
329
        }
2✔
330
        this.setSelectedNodes();
18✔
331
    }
332

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

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

351
        if (this.thyVirtualScroll) {
352
            this.buildFlattenTreeNodes();
353
        }
354

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

377
    onFocus($event: FocusEvent) {
378
        const inputElement: HTMLInputElement = this.elementRef.nativeElement.querySelector('input');
379
        inputElement?.focus();
380
    }
1✔
381

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

391
    ngOnDestroy(): void {
392
        this.destroy$.next();
1✔
393
    }
394

395
    get selectedValueObject() {
396
        return this.thyMultiple ? this.selectedNodes : this.selectedNode;
1✔
397
    }
398

399
    searchValue(searchText: string) {
400
        this.treeNodes = filterTreeData(this.originTreeNodes, searchText.trim(), this.thyShowKey);
1✔
401
    }
402

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

412
    private init() {
413
        this.cdkConnectOverlayWidth = this.cdkOverlayOrigin.elementRef.nativeElement.getBoundingClientRect().width;
414
    }
415

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

434
    private _findTreeNode(value: string): ThyTreeSelectNode {
435
        return (this.flattenTreeNodes || []).find(item => item[this.thyPrimaryKey] === value);
436
    }
437

438
    private setSelectedNodes() {
439
        if (this.selectedValue) {
440
            // 多选数据初始化
441
            if (this.thyMultiple) {
1✔
442
                if (this.selectedValue.length > 0) {
443
                    if (this.valueIsObject && Object.keys(this.selectedValue[0]).indexOf(this.thyPrimaryKey) >= 0) {
444
                        this.selectedNodes = this.selectedValue.map((item: any) => {
445
                            return this._findTreeNode(item[this.thyPrimaryKey]);
1✔
446
                        });
447
                    } else {
13✔
448
                        this.selectedNodes = this.selectedValue.map((item: any) => {
449
                            return this._findTreeNode(item);
13✔
450
                        });
13✔
451
                    }
452
                }
453
            } else {
13✔
454
                // 单选数据初始化
13✔
455
                if (this.valueIsObject) {
13✔
456
                    if (Object.keys(this.selectedValue).indexOf(this.thyPrimaryKey) >= 0) {
13✔
457
                        this.selectedNode = this._findTreeNode(this.selectedValue[this.thyPrimaryKey]);
13✔
458
                    }
13✔
459
                } else {
13✔
460
                    this.selectedNode = this._findTreeNode(this.selectedValue);
13✔
461
                }
13✔
462
            }
13✔
463
        } else {
13✔
464
            this.selectedNodes = [];
13✔
465
            this.selectedNode = null;
466
        }
467
    }
13✔
468

469
    openSelectPop() {
470
        if (this.thyDisable) {
361✔
471
            return;
244!
472
        }
50✔
473
        this.cdkConnectOverlayWidth = this.cdkOverlayOrigin.elementRef.nativeElement.getBoundingClientRect().width;
474
        this.expandTreeSelectOptions = !this.expandTreeSelectOptions;
475
        this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
476
    }
117!
477

478
    close() {
479
        if (this.expandTreeSelectOptions) {
480
            this.expandTreeSelectOptions = false;
418✔
481
            this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
394✔
482
            this.onTouchedFn();
483
        }
24!
484
    }
24✔
485

486
    clearSelectedValue(event: Event) {
×
487
        event.stopPropagation();
488
        this.selectedValue = null;
489
        this.selectedNode = null;
373✔
490
        this.selectedNodes = [];
363✔
491
        this.onChangeFn(this.selectedValue);
492
    }
10!
493

10✔
494
    private _changeSelectValue() {
495
        if (this.valueIsObject) {
×
496
            this.selectedValue = this.thyMultiple ? this.selectedNodes : this.selectedNode;
497
        } else {
498
            this.selectedValue = this.thyMultiple
707✔
499
                ? this.selectedNodes.map(item => item[this.thyPrimaryKey])
707✔
500
                : this.selectedNode[this.thyPrimaryKey];
536!
501
        }
110✔
502
        this.onChangeFn(this.selectedValue);
503
        if (!this.thyMultiple) {
504
            this.onTouchedFn();
505
        }
171!
506
    }
507

508
    removeMultipleSelectedNode(event: { item: ThyTreeSelectNode; $eventOrigin: Event }) {
509
        this.removeSelectedNode(event.item, event.$eventOrigin);
707✔
510
    }
707✔
511

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

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

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

566
const DEFAULT_ITEM_SIZE = 40;
567

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

594
    nodeList: ThyTreeSelectNode[] = [];
595

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

603
    @Input() thyVirtualScroll: boolean = false;
604

605
    public primaryKey = this.parent.thyPrimaryKey;
606

607
    public showKey = this.parent.thyShowKey;
608

609
    public isMultiple = this.parent.thyMultiple;
610

611
    public valueIsObject = this.parent.valueIsObject;
612

613
    public selectedValue = this.parent.selectedValue;
614

615
    public childCountKey = this.parent.thyChildCountKey;
616

617
    public treeNodeTemplateRef = this.parent.treeNodeTemplateRef;
618

619
    public defaultItemSize = DEFAULT_ITEM_SIZE;
620

621
    public thyVirtualHeight: string = null;
622

623
    constructor(public parent: ThyTreeSelectComponent) {}
624

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

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

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

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

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

675
    getNodeChildren(node: ThyTreeSelectNode) {
676
        return this.parent.getNodeChildren(node);
677
    }
678

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

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

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

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