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

atinc / ngx-tethys / cd64db52-e563-41a3-85f3-a0adb87ce135

30 Oct 2024 08:03AM UTC coverage: 90.402% (-0.04%) from 90.438%
cd64db52-e563-41a3-85f3-a0adb87ce135

push

circleci

web-flow
refactor: refactor constructor to the inject function (#3222)

5503 of 6730 branches covered (81.77%)

Branch coverage included in aggregate %.

422 of 429 new or added lines in 170 files covered. (98.37%)

344 existing lines in 81 files now uncovered.

13184 of 13941 relevant lines covered (94.57%)

997.19 hits per line

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

91.57
/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
    inject
18
} from '@angular/core';
15✔
19
import { defer, merge, Observable, Subject, timer } from 'rxjs';
15✔
20
import { take, switchMap, takeUntil, startWith } from 'rxjs/operators';
15✔
21
import { SelectionModel } from '@angular/cdk/collections';
15✔
22
import {
15✔
23
    THY_OPTION_PARENT_COMPONENT,
15✔
24
    IThyOptionParentComponent,
15✔
25
    ThyOption,
15✔
26
    ThyOptionSelectionChangeEvent,
15!
27
    ThyStopPropagationDirective
102✔
28
} from 'ngx-tethys/shared';
UNCOV
29
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
×
30
import { ThyEmpty } from 'ngx-tethys/empty';
31
import { NgClass } from '@angular/common';
15✔
32
import { coerceBooleanProperty } from 'ngx-tethys/util';
15✔
33

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

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

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

64
    private ngUnsubscribe$ = new Subject<void>();
11✔
65

11✔
66
    dropDownClass: { [key: string]: boolean };
67

68
    isMultiple = false;
15✔
69

15✔
70
    mode = '';
5✔
71

72
    isEmptyOptions = false;
73

74
    selectionModel: SelectionModel<ThyOption>;
15!
UNCOV
75

×
76
    isOpened = false;
77

15✔
78
    /** Manages active item in option list based on key events. */
15✔
79
    keyManager: ActiveDescendantKeyManager<ThyOption>;
3✔
80

3✔
81
    @ViewChild('contentTemplate', { static: true })
82
    contentTemplateRef: TemplateRef<any>;
83

84
    // scroll element container
5✔
85
    @ViewChild('panel')
5✔
86
    optionsContainer: ElementRef<any>;
2✔
87

2✔
88
    /**
89
     * @private
90
     */
3!
91
    @ContentChildren(ThyOption, { descendants: true }) options: QueryList<ThyOption>;
3!
92

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

103
    /**
15✔
104
     * 空选项时的文本
15✔
105
     * @type string
3✔
106
     */
107
    @Input()
108
    thyEmptyText = '没有任何数据';
12✔
109

110
    /**
15✔
111
     * 是否默认高亮第一个选项
112
     * @type boolean
113
     * @default false
114
     */
115
    @Input({ transform: coerceBooleanProperty }) thyAutoActiveFirstOption: boolean;
116

15✔
117
    /**
15✔
118
     * 被选中时调用,参数包含选中项的 value 值
119
     * @type EventEmitter<ThyOptionSelectionChangeEvent>
1✔
120
     */
121
    @Output() thyOptionSelected: EventEmitter<ThyOptionSelectionChangeEvent> = new EventEmitter<ThyOptionSelectionChangeEvent>();
122

123
    /**
124
     * 只读,展开下拉菜单的回调
125
     * @type EventEmitter<void>
126
     */
127
    @Output() readonly thyOpened: EventEmitter<void> = new EventEmitter<void>();
128

129
    /**
130
     * 只读,关闭下拉菜单的回调
131
     * @type EventEmitter<void>
1✔
132
     */
133
    @Output() readonly thyClosed: EventEmitter<void> = new EventEmitter<void>();
134

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

142
    ngOnInit() {
143
        this.setDropDownClass();
144
        this.instanceSelectionModel();
145
    }
146

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

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

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

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

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

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

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

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

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

207
            if (isUserInput) {
208
                this.keyManager.setActiveItem(option);
209
            }
210
        }
211

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

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

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

© 2026 Coveralls, Inc