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

atinc / ngx-tethys / d47d4d0a-8f43-4945-8a11-2c3d54c10acb

19 Nov 2024 10:33AM UTC coverage: 90.362%. Remained the same
d47d4d0a-8f43-4945-8a11-2c3d54c10acb

push

circleci

web-flow
feat(autocomplete): support i18n #TINFR-1020 (#3259)

5524 of 6762 branches covered (81.69%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

13218 of 13979 relevant lines covered (94.56%)

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

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

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

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

66
    private ngUnsubscribe$ = new Subject<void>();
11✔
67

11✔
68
    private locale: Signal<ThyAutoCompleteLocale> = injectLocale('autocomplete');
69

70
    dropDownClass: { [key: string]: boolean };
15✔
71

15✔
72
    isMultiple = false;
5✔
73

74
    mode = '';
75

76
    isEmptyOptions = false;
15!
UNCOV
77

×
78
    selectionModel: SelectionModel<ThyOption>;
79

15✔
80
    isOpened = false;
15✔
81

3✔
82
    /** Manages active item in option list based on key events. */
3✔
83
    keyManager: ActiveDescendantKeyManager<ThyOption>;
84

85
    @ViewChild('contentTemplate', { static: true })
86
    contentTemplateRef: TemplateRef<any>;
5✔
87

5✔
88
    // scroll element container
2✔
89
    @ViewChild('panel')
2✔
90
    optionsContainer: ElementRef<any>;
91

92
    /**
3!
93
     * @private
3!
94
     */
95
    @ContentChildren(ThyOption, { descendants: true }) options: QueryList<ThyOption>;
3!
96

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

15✔
107
    /**
3✔
108
     * 空选项时的文本
109
     * @default 没有任何数据
110
     */
12✔
111
    @Input() thyEmptyText = this.locale().empty;
112

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

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

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

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

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

145
    ngOnInit() {
146
        this.setDropDownClass();
147
        this.instanceSelectionModel();
148
    }
149

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

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

169
    open() {
170
        this.isOpened = true;
171
        this.changeDetectorRef.markForCheck();
172
        this.thyOpened.emit();
173
    }
174

175
    close() {
176
        this.isOpened = false;
177
        this.thyClosed.emit();
178
    }
179

180
    private resetOptions() {
181
        const changedOrDestroyed$ = merge(this.options.changes, this.ngUnsubscribe$);
182

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

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

199
    private onSelect(option: ThyOption, isUserInput: boolean) {
200
        const wasSelected = this.selectionModel.isSelected(option);
201

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

210
            if (isUserInput) {
211
                this.keyManager.setActiveItem(option);
212
            }
213
        }
214

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

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

234
    ngOnDestroy() {
235
        this.ngUnsubscribe$.next();
236
        this.ngUnsubscribe$.complete();
237
    }
238
}
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