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

atinc / ngx-tethys / e62d3b10-1466-49c3-aabd-707148681fc8

14 Jun 2024 08:24AM UTC coverage: 90.422%. Remained the same
e62d3b10-1466-49c3-aabd-707148681fc8

push

circleci

minlovehua
feat: use the ngx-tethys/util's coerceBooleanProperty instead of booleanAttribute #INFR-12648

5467 of 6692 branches covered (81.69%)

Branch coverage included in aggregate %.

117 of 120 new or added lines in 66 files covered. (97.5%)

183 existing lines in 46 files now uncovered.

13216 of 13970 relevant lines covered (94.6%)

985.91 hits per line

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

91.67
/src/autocomplete/autocomplete.component.ts
1
import {
2
    Component,
3
    TemplateRef,
4
    ViewChild,
5
    ChangeDetectionStrategy,
6
    ContentChildren,
7
    QueryList,
8
    OnInit,
9
    Output,
10
    EventEmitter,
11
    NgZone,
12
    OnDestroy,
13
    AfterContentInit,
14
    ChangeDetectorRef,
15
    Input,
16
    ElementRef
1✔
17
} from '@angular/core';
18
import { defer, merge, Observable, Subject, timer } from 'rxjs';
15✔
19
import { take, switchMap, takeUntil, startWith } from 'rxjs/operators';
15✔
20
import { SelectionModel } from '@angular/cdk/collections';
15✔
21
import {
15✔
22
    THY_OPTION_PARENT_COMPONENT,
15✔
23
    IThyOptionParentComponent,
15✔
24
    ThyOption,
15✔
25
    ThyOptionSelectionChangeEvent,
15✔
26
    ThyStopPropagationDirective
15!
27
} from 'ngx-tethys/shared';
102✔
28
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
UNCOV
29
import { ThyEmpty } from 'ngx-tethys/empty';
×
30
import { NgClass, NgIf } from '@angular/common';
31
import { coerceBooleanProperty } from 'ngx-tethys/util';
15✔
32

15✔
33
/** Event object that is emitted when an autocomplete option is activated. */
15✔
34
export interface ThyAutocompleteActivatedEvent {
15✔
35
    /** Reference to the autocomplete panel that emitted the event. */
15✔
36
    source: ThyAutocomplete;
37

38
    /** Option that was selected. */
15✔
39
    option: ThyOption | null;
15✔
40
}
41

42
/**
15✔
43
 * 自动完成组件
15✔
44
 * @name thy-autocomplete
15✔
45
 */
15✔
46
@Component({
15✔
47
    selector: 'thy-autocomplete',
48
    templateUrl: 'autocomplete.component.html',
15✔
49
    changeDetection: ChangeDetectionStrategy.OnPush,
50
    providers: [
51
        {
52
            provide: THY_OPTION_PARENT_COMPONENT,
15✔
53
            useExisting: ThyAutocomplete
15✔
54
        }
15✔
55
    ],
8✔
56
    standalone: true,
57
    imports: [ThyStopPropagationDirective, NgClass, NgIf, ThyEmpty]
58
})
59
export class ThyAutocomplete implements IThyOptionParentComponent, OnInit, AfterContentInit, OnDestroy {
11✔
60
    private ngUnsubscribe$ = new Subject<void>();
11✔
61

11✔
62
    dropDownClass: { [key: string]: boolean };
63

64
    isMultiple = false;
11✔
65

11✔
66
    mode = '';
67

68
    isEmptyOptions = false;
15✔
69

15✔
70
    selectionModel: SelectionModel<ThyOption>;
5✔
71

72
    isOpened = false;
73

74
    /** Manages active item in option list based on key events. */
15!
UNCOV
75
    keyManager: ActiveDescendantKeyManager<ThyOption>;
×
76

77
    @ViewChild('contentTemplate', { static: true })
15✔
78
    contentTemplateRef: TemplateRef<any>;
15✔
79

3✔
80
    // scroll element container
3✔
81
    @ViewChild('panel')
82
    optionsContainer: ElementRef<any>;
83

84
    /**
5✔
85
     * @private
5✔
86
     */
2✔
87
    @ContentChildren(ThyOption, { descendants: true }) options: QueryList<ThyOption>;
2✔
88

89
    readonly optionSelectionChanges: Observable<ThyOptionSelectionChangeEvent> = defer(() => {
90
        if (this.options) {
3!
91
            return merge(...this.options.map(option => option.selectionChange));
3!
92
        }
93
        return this.ngZone.onStable.asObservable().pipe(
3!
94
            take(1),
3✔
95
            switchMap(() => this.optionSelectionChanges)
96
        );
97
    }) as Observable<ThyOptionSelectionChangeEvent>;
5✔
98

3✔
99
    /**
100
     * 空选项时的文本
5✔
101
     * @type string
102
     */
103
    @Input()
15✔
104
    thyEmptyText = '没有任何数据';
15✔
105

3✔
106
    /**
107
     * 是否默认高亮第一个选项
108
     * @type boolean
12✔
109
     * @default false
110
     */
15✔
111
    @Input({ transform: coerceBooleanProperty }) thyAutoActiveFirstOption: boolean;
112

113
    /**
114
     * 被选中时调用,参数包含选中项的 value 值
115
     * @type EventEmitter<ThyOptionSelectionChangeEvent>
116
     */
15✔
117
    @Output() thyOptionSelected: EventEmitter<ThyOptionSelectionChangeEvent> = new EventEmitter<ThyOptionSelectionChangeEvent>();
15✔
118

119
    /**
1✔
120
     * 只读,展开下拉菜单的回调
121
     * @type EventEmitter<void>
122
     */
123
    @Output() readonly thyOpened: EventEmitter<void> = new EventEmitter<void>();
1✔
124

125
    /**
126
     * 只读,关闭下拉菜单的回调
127
     * @type EventEmitter<void>
128
     */
129
    @Output() readonly thyClosed: EventEmitter<void> = new EventEmitter<void>();
130

131
    /** Emits whenever an option is activated using the keyboard. */
132
    /**
133
     * 只读,option 激活状态变化时,调用此函数
134
     * @type EventEmitter<ThyAutocompleteActivatedEvent>
135
     */
1✔
136
    @Output() readonly thyOptionActivated: EventEmitter<ThyAutocompleteActivatedEvent> = new EventEmitter<ThyAutocompleteActivatedEvent>();
137

138
    constructor(private ngZone: NgZone, private changeDetectorRef: ChangeDetectorRef) {}
139

140
    ngOnInit() {
141
        this.setDropDownClass();
142
        this.instanceSelectionModel();
143
    }
144

145
    ngAfterContentInit() {
146
        this.options.changes.pipe(startWith(null), takeUntil(this.ngUnsubscribe$)).subscribe(() => {
147
            this.resetOptions();
148
            timer(0).subscribe(() => {
149
                this.isEmptyOptions = this.options.length <= 0;
150
                this.changeDetectorRef.detectChanges();
151
            });
152
            this.initKeyManager();
153
        });
154
    }
155

156
    initKeyManager() {
157
        const changedOrDestroyed$ = merge(this.options.changes, this.ngUnsubscribe$);
158
        this.keyManager = new ActiveDescendantKeyManager<ThyOption>(this.options).withWrap();
159
        this.keyManager.change.pipe(takeUntil(changedOrDestroyed$)).subscribe(index => {
160
            this.thyOptionActivated.emit({ source: this, option: this.options.toArray()[index] || null });
161
        });
162
    }
163

164
    open() {
165
        this.isOpened = true;
166
        this.changeDetectorRef.markForCheck();
167
        this.thyOpened.emit();
168
    }
169

170
    close() {
171
        this.isOpened = false;
172
        this.thyClosed.emit();
173
    }
174

175
    private resetOptions() {
176
        const changedOrDestroyed$ = merge(this.options.changes, this.ngUnsubscribe$);
177

178
        this.optionSelectionChanges.pipe(takeUntil(changedOrDestroyed$)).subscribe((event: ThyOptionSelectionChangeEvent) => {
179
            this.onSelect(event.option, event.isUserInput);
180
        });
181
    }
182

183
    private instanceSelectionModel() {
184
        if (this.selectionModel) {
185
            this.selectionModel.clear();
186
        }
187
        this.selectionModel = new SelectionModel<ThyOption>(this.isMultiple);
188
        this.selectionModel.changed.pipe(takeUntil(this.ngUnsubscribe$)).subscribe(event => {
189
            event.added.forEach(option => option.select());
190
            event.removed.forEach(option => option.deselect());
191
        });
192
    }
193

194
    private onSelect(option: ThyOption, isUserInput: boolean) {
195
        const wasSelected = this.selectionModel.isSelected(option);
196

197
        if (option.thyValue == null && !this.isMultiple) {
198
            option.deselect();
199
            this.selectionModel.clear();
200
        } else {
201
            if (wasSelected !== option.selected) {
202
                option.selected ? this.selectionModel.select(option) : this.selectionModel.deselect(option);
203
            }
204

205
            if (isUserInput) {
206
                this.keyManager.setActiveItem(option);
207
            }
208
        }
209

210
        if (wasSelected !== this.selectionModel.isSelected(option)) {
211
            this.thyOptionSelected.emit(new ThyOptionSelectionChangeEvent(option, false));
212
        }
213
        this.changeDetectorRef.markForCheck();
214
    }
215

216
    private setDropDownClass() {
217
        let modeClass = '';
218
        if (this.isMultiple) {
219
            modeClass = `thy-select-dropdown-${this.mode}`;
220
        } else {
221
            modeClass = `thy-select-dropdown-single`;
222
        }
223
        this.dropDownClass = {
224
            [`thy-select-dropdown`]: true,
225
            [modeClass]: true
226
        };
227
    }
228

229
    ngOnDestroy() {
230
        this.ngUnsubscribe$.next();
231
        this.ngUnsubscribe$.complete();
232
    }
233
}
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