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

atinc / ngx-tethys / c0ef8457-a839-451f-8b72-80fd73106231

02 Apr 2024 02:27PM UTC coverage: 90.524% (-0.06%) from 90.585%
c0ef8457-a839-451f-8b72-80fd73106231

Pull #3062

circleci

minlovehua
refactor(all): use the transform attribute of @Input() instead of @InputBoolean() and @InputNumber()
Pull Request #3062: refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()

4987 of 6108 branches covered (81.65%)

Branch coverage included in aggregate %.

217 of 223 new or added lines in 82 files covered. (97.31%)

202 existing lines in 53 files now uncovered.

12246 of 12929 relevant lines covered (94.72%)

1055.59 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,
1✔
13
    AfterContentInit,
14
    ChangeDetectorRef,
15
    Input,
16
    ElementRef,
17
    booleanAttribute
1✔
18
} from '@angular/core';
19
import { Constructor, ThyUnsubscribe } from 'ngx-tethys/core';
15✔
20
import { defer, merge, Observable, timer } from 'rxjs';
15✔
21
import { take, switchMap, takeUntil, startWith } from 'rxjs/operators';
15✔
22
import { MixinBase, mixinUnsubscribe } from 'ngx-tethys/core';
15✔
23
import { SelectionModel } from '@angular/cdk/collections';
15✔
24
import {
15✔
25
    THY_OPTION_PARENT_COMPONENT,
15✔
26
    IThyOptionParentComponent,
15✔
27
    ThyOption,
15!
28
    ThyOptionSelectionChangeEvent,
102✔
29
    ThyStopPropagationDirective
UNCOV
30
} from 'ngx-tethys/shared';
×
31
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
32
import { ThyEmpty } from 'ngx-tethys/empty';
15✔
33
import { NgClass, NgIf } from '@angular/common';
15✔
34

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

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

15✔
44
const _MixinBase: Constructor<ThyUnsubscribe> & typeof MixinBase = mixinUnsubscribe(MixinBase);
15✔
45

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

11✔
66
    isMultiple = false;
11✔
67

68
    mode = '';
69

15✔
70
    isEmptyOptions = false;
15✔
71

5✔
72
    selectionModel: SelectionModel<ThyOption>;
73

74
    isOpened = false;
75

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

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

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

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

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

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

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

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

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

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

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

140
    constructor(private ngZone: NgZone, private changeDetectorRef: ChangeDetectorRef) {
141
        super();
1✔
142
    }
143

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

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

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

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

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

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

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

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

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

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

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

213
            // if (this.isMultiple) {
214
            //     this.sortValues();
215
            //     if (isUserInput) {
216
            //         this.focus();
217
            //     }
218
            // }
219
        }
220

221
        if (wasSelected !== this.selectionModel.isSelected(option)) {
222
            this.thyOptionSelected.emit(new ThyOptionSelectionChangeEvent(option, false));
223
        }
224
        this.changeDetectorRef.markForCheck();
225
    }
226

227
    private setDropDownClass() {
228
        let modeClass = '';
229
        if (this.isMultiple) {
230
            modeClass = `thy-select-dropdown-${this.mode}`;
231
        } else {
232
            modeClass = `thy-select-dropdown-single`;
233
        }
234
        this.dropDownClass = {
235
            [`thy-select-dropdown`]: true,
236
            [modeClass]: true
237
        };
238
    }
239

240
    ngOnDestroy() {
241
        super.ngOnDestroy();
242
    }
243
}
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