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

atinc / ngx-tethys / 68ef226c-f83e-44c1-b8ed-e420a83c5d84

28 May 2025 10:31AM UTC coverage: 10.352% (-80.0%) from 90.316%
68ef226c-f83e-44c1-b8ed-e420a83c5d84

Pull #3460

circleci

pubuzhixing8
chore: xxx
Pull Request #3460: refactor(icon): migrate signal input #TINFR-1476

132 of 6823 branches covered (1.93%)

Branch coverage included in aggregate %.

10 of 14 new or added lines in 1 file covered. (71.43%)

11648 existing lines in 344 files now uncovered.

2078 of 14525 relevant lines covered (14.31%)

6.69 hits per line

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

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

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

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

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

65
    private ngUnsubscribe$ = new Subject<void>();
66

UNCOV
67
    private locale: Signal<ThyAutoCompleteLocale> = injectLocale('autocomplete');
×
UNCOV
68

×
UNCOV
69
    emptyIcon: Signal<string> = injectPanelEmptyIcon();
×
70

71
    dropDownClass: { [key: string]: boolean };
UNCOV
72

×
UNCOV
73
    isMultiple = false;
×
74

75
    mode = '';
UNCOV
76

×
UNCOV
77
    isEmptyOptions = false;
×
UNCOV
78

×
79
    selectionModel: SelectionModel<ThyOption>;
80

81
    isOpened = false;
UNCOV
82

×
83
    /** Manages active item in option list based on key events. */
×
84
    keyManager: ActiveDescendantKeyManager<ThyOption>;
UNCOV
85

×
UNCOV
86
    readonly contentTemplateRef = viewChild<TemplateRef<any>>('contentTemplate');
×
UNCOV
87

×
UNCOV
88
    // scroll element container
×
89
    readonly optionsContainer = viewChild<ElementRef<any>>('panel');
90

91
    /**
UNCOV
92
     * @private
×
UNCOV
93
     */
×
UNCOV
94
    @ContentChildren(ThyOption, { descendants: true }) options: QueryList<ThyOption>;
×
UNCOV
95

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

×
UNCOV
106
    /**
×
107
     * 空选项时的文本
UNCOV
108
     * @default 没有任何数据
×
109
     */
110
    readonly thyEmptyText = input<string>(this.locale().empty);
UNCOV
111

×
UNCOV
112
    /**
×
UNCOV
113
     * 是否默认高亮第一个选项
×
114
     */
115
    readonly thyAutoActiveFirstOption = input(false, { transform: coerceBooleanProperty });
UNCOV
116

×
117
    /**
UNCOV
118
     * 被选中时调用,参数包含选中项的 value 值
×
119
     */
120
    readonly thyOptionSelected = output<ThyOptionSelectionChangeEvent>();
121

122
    /**
123
     * 只读,展开下拉菜单的回调
UNCOV
124
     */
×
UNCOV
125
    readonly thyOpened = output<void>();
×
126

127
    /**
1✔
128
     * 只读,关闭下拉菜单的回调
129
     */
130
    readonly thyClosed = output<void>();
131

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

138
    ngOnInit() {
139
        this.setDropDownClass();
1✔
140
        this.instanceSelectionModel();
141
    }
142

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

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

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

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

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

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

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

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

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

203
            if (isUserInput) {
204
                this.keyManager.setActiveItem(option);
205
            }
206
        }
207

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

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

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