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

atinc / ngx-tethys / 8a6ba229-c82f-4a21-a1ed-95461f2ad66c

04 Sep 2023 08:37AM UTC coverage: 90.196% (-0.004%) from 90.2%
8a6ba229-c82f-4a21-a1ed-95461f2ad66c

Pull #2829

circleci

cmm-va
fix: delete f
Pull Request #2829: fix: add tabIndex

5164 of 6386 branches covered (0.0%)

Branch coverage included in aggregate %.

78 of 78 new or added lines in 26 files covered. (100.0%)

13024 of 13779 relevant lines covered (94.52%)

971.69 hits per line

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

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

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

29
import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
30
import { ThyEmptyComponent } from 'ngx-tethys/empty';
15✔
31
import { ThyIconComponent } from 'ngx-tethys/icon';
32
import { ThySelectControlComponent, ThyStopPropagationDirective } from 'ngx-tethys/shared';
4✔
33
import { ThyTreeSelectNode, ThyTreeSelectType } from './tree-select.class';
2✔
34

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

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

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

20✔
90
    @HostBinding('class.thy-select') isTreeSelect = true;
20✔
91

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

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

38✔
97
    public treeNodes: ThyTreeSelectNode[];
38✔
98

38✔
99
    public selectedValue: any;
38✔
100

38✔
101
    public selectedNode: ThyTreeSelectNode;
38✔
102

38✔
103
    public selectedNodes: ThyTreeSelectNode[] = [];
38✔
104

38✔
105
    public flattenTreeNodes: ThyTreeSelectNode[] = [];
38✔
106

38✔
107
    virtualTreeNodes: ThyTreeSelectNode[] = [];
38✔
108

38✔
109
    public cdkConnectOverlayWidth = 0;
38✔
110

38✔
111
    public positions: ConnectionPositionPair[];
38✔
112

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

38✔
119
    private initialled = false;
38✔
120

38✔
121
    private destroy$ = new Subject<void>();
38✔
122

38✔
123
    public valueIsObject = false;
38✔
124

38✔
125
    originTreeNodes: ThyTreeSelectNode[];
38✔
126

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

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

38✔
133
    @ViewChild(CdkOverlayOrigin, { static: true }) cdkOverlayOrigin: CdkOverlayOrigin;
38✔
134

38✔
135
    @ViewChild(CdkConnectedOverlay, { static: true }) cdkConnectedOverlay: CdkConnectedOverlay;
38✔
136

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

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

27✔
153
    /**
2✔
154
     * 开启虚拟滚动
2✔
155
     */
2✔
156
    @Input() @InputBoolean() thyVirtualScroll: boolean = false;
157

158
    /**
159
     * 树节点的唯一标识
160
     * @type string
38✔
161
     */
162
    @Input() thyPrimaryKey = '_id';
163

164
    /**
1✔
165
     * 树节点的显示的字段 key
166
     * @type string
167
     */
168
    @Input() thyShowKey = 'name';
1✔
169

1✔
170
    @Input() thyChildCountKey = 'childCount';
171

172
    /**
173
     * 单选时,是否显示清除按钮,当为 true 时,显示清除按钮
174
     * @default false
2✔
175
     */
1✔
176
    @Input() @InputBoolean() thyAllowClear: boolean;
177

1✔
178
    /**
179
     * 是否多选
180
     * @type boolean
38✔
181
     */
182
    @Input() @InputBoolean() thyMultiple = false;
183

188✔
184
    /**
185
     * 是否禁用树选择器,当为 true 禁用树选择器
186
     * @type boolean
1✔
187
     */
188
    @Input() @InputBoolean() thyDisable = false;
189

1✔
190
    get thyDisabled(): boolean {
191
        return this.thyDisable;
192
    }
193

1✔
194
    /**
195
     * 树选择框默认文字
196
     * @type string
197
     */
1✔
198
    @Input() thyPlaceholder = '请选择节点';
199

×
200
    get placeholder() {
3,146✔
201
        return this.thyPlaceholder;
3,146✔
202
    }
3,146!
203

84,107✔
204
    /**
84,107✔
205
     * 控制树选择的输入框大小
84,107✔
206
     * @type xs | sm | md | default | lg
3,107✔
207
     */
3,107✔
208
    @Input() thySize: InputSize;
209

210
    /**
3,146✔
211
     * 改变空选项的情况下的提示文本
212
     * @type string
213
     */
28!
214
    @Input() thyEmptyOptionsText = '暂时没有数据可选';
215

216
    /**
58✔
217
     * 设置是否隐藏节点(不可进行任何操作),优先级高于 thyHiddenNodeFn
218
     * @type string
4✔
219
     */
2!
220
    @Input() thyHiddenNodeKey = 'hidden';
2✔
221

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

2✔
228
    /**
229
     * 是否异步加载节点的子节点(显示加载状态),当为 true 时,异步获取
230
     * @type boolean
231
     */
232
    @Input() @InputBoolean() thyAsyncNode = false;
233

234
    /**
2✔
235
     * 是否展示全名
1!
236
     * @type boolean
1✔
237
     */
238
    @Input() @InputBoolean() thyShowWholeName = false;
239

240
    /**
1✔
241
     * 是否展示搜索
242
     * @type boolean
243
     */
244
    @Input() @InputBoolean() thyShowSearch = false;
245

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

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

2✔
268
    /**
2✔
269
     * 设置是否禁用节点(不可进行任何操作),优先级低于 thyDisableNodeKey。
2✔
270
     * @default (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.disabled
271
     */
272
    @Input() thyDisableNodeFn: (node: ThyTreeSelectNode) => boolean = (node: ThyTreeSelectNode) => node.disabled;
13!
273

×
274
    /**
275
     * 获取节点的子节点,返回 Observable<ThyTreeSelectNode>。
276
     * @default (node: ThyTreeSelectNode) => Observable<ThyTreeSelectNode> = (node: ThyTreeSelectNode) => of([])
13✔
277
     */
7✔
278
    @Input() thyGetNodeChildren: (node: ThyTreeSelectNode) => Observable<ThyTreeSelectNode> = (node: ThyTreeSelectNode) => of([]);
279

280
    /**
13✔
281
     * 树选择组件展开和折叠状态事件
13✔
282
     */
3✔
283
    @Output() thyExpandStatusChange: EventEmitter<boolean> = new EventEmitter<boolean>();
284

285
    private _getNgModelType() {
286
        if (this.thyMultiple) {
1✔
287
            this.valueIsObject = !this.selectedValue[0] || isObject(this.selectedValue[0]);
288
        } else {
289
            this.valueIsObject = isObject(this.selectedValue);
290
        }
3✔
291
    }
1✔
292

293
    public buildFlattenTreeNodes() {
3!
294
        this.virtualTreeNodes = this.getFlattenTreeNodes(this.treeNodes);
×
295
    }
296

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

310
    writeValue(value: any): void {
311
        this.selectedValue = value;
9✔
312

2✔
313
        if (value) {
314
            this._getNgModelType();
2✔
315
        }
316
        this.setSelectedNodes();
317
    }
7✔
318

7✔
319
    constructor(
320
        public elementRef: ElementRef,
321
        private ngZone: NgZone,
322
        private ref: ChangeDetectorRef,
323
        @Inject(PLATFORM_ID) private platformId: string,
1✔
324
        private thyClickDispatcher: ThyClickDispatcher,
1!
325
        private viewportRuler: ViewportRuler
1✔
326
    ) {
1✔
327
        super();
1✔
328
    }
2✔
329

18✔
330
    ngOnInit() {
331
        this.positions = getFlexiblePositions('bottom', 4);
332
        this.isMulti = this.thyMultiple;
1✔
333
        this.flattenTreeNodes = this.flattenNodes(this.treeNodes, this.flattenTreeNodes, []);
1✔
334
        this.setSelectedNodes();
335
        this.initialled = true;
1✔
336

337
        if (this.thyVirtualScroll) {
338
            this.buildFlattenTreeNodes();
1✔
339
        }
340

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

363
    onFocus($event: FocusEvent) {
364
        const inputElement: HTMLInputElement = this.elementRef.nativeElement.querySelector('input');
365
        inputElement?.focus();
366
    }
367

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

377
    ngOnDestroy(): void {
378
        this.destroy$.next();
379
    }
1✔
380

381
    get selectedValueObject() {
382
        return this.thyMultiple ? this.selectedNodes : this.selectedNode;
383
    }
1✔
384

385
    searchValue(searchText: string) {
386
        this.treeNodes = filterTreeData(this.originTreeNodes, searchText.trim(), this.thyShowKey);
387
    }
1✔
388

389
    public setPosition() {
390
        this.ngZone.onStable
391
            .asObservable()
1✔
392
            .pipe(take(1))
393
            .subscribe(() => {
394
                this.cdkConnectedOverlay.overlayRef.updatePosition();
395
            });
1✔
396
    }
397

398
    private init() {
399
        this.cdkConnectOverlayWidth = this.cdkOverlayOrigin.elementRef.nativeElement.getBoundingClientRect().width;
1✔
400
    }
401

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

420
    private _findTreeNode(value: string): ThyTreeSelectNode {
421
        return (this.flattenTreeNodes || []).find(item => item[this.thyPrimaryKey] === value);
422
    }
423

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

13✔
455
    openSelectPop() {
13✔
456
        if (this.thyDisable) {
13✔
457
            return;
13✔
458
        }
13✔
459
        this.cdkConnectOverlayWidth = this.cdkOverlayOrigin.elementRef.nativeElement.getBoundingClientRect().width;
13✔
460
        this.expandTreeSelectOptions = !this.expandTreeSelectOptions;
13✔
461
        this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
13✔
462
    }
13✔
463

13✔
464
    close() {
465
        if (this.expandTreeSelectOptions) {
466
            this.expandTreeSelectOptions = false;
13✔
467
            this.thyExpandStatusChange.emit(this.expandTreeSelectOptions);
468
            this.onTouchedFn();
469
        }
361✔
470
    }
244!
471

50✔
472
    clearSelectedValue(event: Event) {
473
        event.stopPropagation();
474
        this.selectedValue = null;
475
        this.selectedNode = null;
117!
476
        this.selectedNodes = [];
477
        this.onChangeFn(this.selectedValue);
478
    }
479

418✔
480
    private _changeSelectValue() {
394✔
481
        if (this.valueIsObject) {
482
            this.selectedValue = this.thyMultiple ? this.selectedNodes : this.selectedNode;
24!
483
        } else {
24✔
484
            this.selectedValue = this.thyMultiple
485
                ? this.selectedNodes.map(item => item[this.thyPrimaryKey])
×
486
                : this.selectedNode[this.thyPrimaryKey];
487
        }
488
        this.onChangeFn(this.selectedValue);
373✔
489
        if (!this.thyMultiple) {
363✔
490
            this.onTouchedFn();
491
        }
10!
492
    }
10✔
493

494
    removeMultipleSelectedNode(event: { item: ThyTreeSelectNode; $eventOrigin: Event }) {
×
495
        this.removeSelectedNode(event.item, event.$eventOrigin);
496
    }
497

707✔
498
    // thyMultiple = true 时,移除数据时调用
707✔
499
    removeSelectedNode(node: ThyTreeSelectNode, event?: Event) {
536!
500
        if (event) {
110✔
501
            event.stopPropagation();
502
        }
503
        if (this.thyDisable) {
504
            return;
171!
505
        }
506
        if (this.thyMultiple) {
507
            this.selectedNodes = produce(this.selectedNodes).remove((item: ThyTreeSelectNode) => {
508
                return item[this.thyPrimaryKey] === node[this.thyPrimaryKey];
707✔
509
            });
707✔
510
            this._changeSelectValue();
707✔
511
        }
512
    }
513

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

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

552
const DEFAULT_ITEM_SIZE = 40;
553

554
/**
555
 * @private
1✔
556
 */
557
@Component({
558
    selector: 'thy-tree-select-nodes',
559
    templateUrl: './tree-select-nodes.component.html',
560
    standalone: true,
561
    imports: [
562
        NgIf,
563
        NgFor,
564
        NgTemplateOutlet,
565
        CdkVirtualScrollViewport,
566
        CdkFixedSizeVirtualScroll,
567
        CdkVirtualForOf,
568
        ThyEmptyComponent,
569
        NgClass,
570
        NgStyle,
571
        ThyIconComponent
572
    ],
573
    host: {
574
        '[attr.tabindex]': '-1'
575
    }
576
})
577
export class ThyTreeSelectNodesComponent implements OnInit {
578
    @HostBinding('class') class: string;
579

580
    nodeList: ThyTreeSelectNode[] = [];
581

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

589
    @Input() thyVirtualScroll: boolean = false;
590

591
    public primaryKey = this.parent.thyPrimaryKey;
592

593
    public showKey = this.parent.thyShowKey;
594

595
    public isMultiple = this.parent.thyMultiple;
596

597
    public valueIsObject = this.parent.valueIsObject;
598

599
    public selectedValue = this.parent.selectedValue;
600

601
    public childCountKey = this.parent.thyChildCountKey;
602

603
    public treeNodeTemplateRef = this.parent.treeNodeTemplateRef;
604

605
    public defaultItemSize = DEFAULT_ITEM_SIZE;
606

607
    public thyVirtualHeight: string = null;
608

609
    constructor(public parent: ThyTreeSelectComponent) {}
610

611
    ngOnInit() {
612
        this.class = this.isMultiple ? 'thy-tree-select-dropdown thy-tree-select-dropdown-multiple' : 'thy-tree-select-dropdown';
613
    }
614

615
    treeNodeIsSelected(node: ThyTreeSelectNode) {
616
        if (this.parent.thyMultiple) {
617
            return (this.parent.selectedNodes || []).find(item => {
618
                return item[this.primaryKey] === node[this.primaryKey];
619
            });
620
        } else {
621
            return this.parent.selectedNode && this.parent.selectedNode[this.primaryKey] === node[this.primaryKey];
622
        }
623
    }
624

625
    treeNodeIsHidden(node: ThyTreeSelectNode) {
626
        if (this.parent.thyHiddenNodeKey) {
627
            return node[this.parent.thyHiddenNodeKey];
628
        }
629
        if (this.parent.thyHiddenNodeFn) {
630
            return this.parent.thyHiddenNodeFn(node);
631
        }
632
        return false;
633
    }
634

635
    treeNodeIsDisable(node: ThyTreeSelectNode) {
636
        if (this.parent.thyDisableNodeKey) {
637
            return node[this.parent.thyDisableNodeKey];
638
        }
639
        if (this.parent.thyDisableNodeFn) {
640
            return this.parent.thyDisableNodeFn(node);
641
        }
642
        return false;
643
    }
644

645
    treeNodeIsExpand(node: ThyTreeSelectNode) {
646
        let isSelectedNodeParent = false;
647
        if (this.parent.thyMultiple) {
648
            isSelectedNodeParent = !!(this.parent.selectedNodes || []).find(item => {
649
                return item.parentValues.indexOf(node[this.primaryKey]) > -1;
650
            });
651
        } else {
652
            isSelectedNodeParent = this.parent.selectedNode
653
                ? this.parent.selectedNode.parentValues.indexOf(node[this.primaryKey]) > -1
654
                : false;
655
        }
656
        const isExpand = node.expand || (Object.keys(node).indexOf('expand') < 0 && isSelectedNodeParent);
657
        node.expand = isExpand;
658
        return isExpand;
659
    }
660

661
    getNodeChildren(node: ThyTreeSelectNode) {
662
        return this.parent.getNodeChildren(node);
663
    }
664

665
    selectTreeNode(event: Event, node: ThyTreeSelectNode) {
666
        if (!this.treeNodeIsDisable(node)) {
667
            this.parent.selectNode(node);
668
        }
669
    }
670

671
    nodeExpandToggle(event: Event, node: ThyTreeSelectNode) {
672
        event.stopPropagation();
673
        if (Object.keys(node).indexOf('expand') > -1) {
674
            node.expand = !node.expand;
675
        } else {
676
            if (this.treeNodeIsExpand(node)) {
677
                node.expand = false;
678
            } else {
679
                node.expand = true;
680
            }
681
        }
682

683
        if (node.expand && this.parent.thyAsyncNode) {
684
            this.getNodeChildren(node).subscribe(() => {
685
                this.parent.setPosition();
686
            });
687
        }
688
        // this.parent.setPosition();
689
        if (this.thyVirtualScroll) {
690
            this.parent.buildFlattenTreeNodes();
691
        }
692
    }
693

694
    tabTrackBy(index: number, item: ThyTreeSelectNode) {
695
        return index;
696
    }
697
}
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