• 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

89.84
/src/shared/select/select-control/select-control.component.ts
1
import { ThyTagSize } from 'ngx-tethys/tag';
2
import { coerceBooleanProperty, isUndefinedOrNull } from 'ngx-tethys/util';
3

4
import {
5
    ChangeDetectionStrategy,
6
    Component,
7
    ElementRef,
8
    EventEmitter,
9
    Input,
10
    OnInit,
11
    Output,
12
    Renderer2,
13
    TemplateRef,
14
    ViewChild,
1✔
15
    numberAttribute
16
} from '@angular/core';
×
17
import { useHostRenderer } from '@tethys/cdk/dom';
18

19
import { NgClass, NgFor, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common';
460✔
20
import { FormsModule } from '@angular/forms';
460✔
21
import { ThyIcon } from 'ngx-tethys/icon';
18✔
22
import { ThyTag } from 'ngx-tethys/tag';
18✔
23
import { SelectOptionBase } from '../../option/select-option-base';
24
import { ThyGridModule } from 'ngx-tethys/grid';
25

460✔
26
export type SelectControlSize = 'sm' | 'md' | 'lg' | '';
6✔
27

6✔
28
/**
29
 * @private
30
 */
460✔
31
@Component({
32
    selector: 'thy-select-control,[thySelectControl]',
UNCOV
33
    templateUrl: './select-control.component.html',
×
34
    changeDetection: ChangeDetectionStrategy.OnPush,
35
    standalone: true,
36
    imports: [FormsModule, NgClass, NgIf, NgStyle, NgFor, ThyTag, NgTemplateOutlet, ThyIcon, ThyGridModule],
297✔
37
    host: {
297✔
38
        '[class.select-control-borderless]': 'thyBorderless'
39
    }
40
})
2,422✔
41
export class ThySelectControl implements OnInit {
42
    inputValue = '';
43

297✔
44
    isComposing = false;
297✔
45

46
    panelOpened = false;
47

2,357✔
48
    isMultiple = false;
49

50
    showSearch = false;
422✔
51

422✔
52
    disabled = false;
422✔
53

75✔
54
    size: SelectControlSize;
8✔
55

56
    selectedOptions: SelectOptionBase | SelectOptionBase[];
57

58
    searchInputControlClass: { [key: string]: boolean };
347✔
59

6✔
60
    tagSize: ThyTagSize;
61

62
    private hostRenderer = useHostRenderer();
422✔
63

422✔
64
    @Input({ transform: coerceBooleanProperty })
4✔
65
    get thyPanelOpened(): boolean {
3✔
66
        return this.panelOpened;
3✔
67
    }
68

69
    set thyPanelOpened(value: boolean) {
70
        this.panelOpened = value;
4✔
71
        if (this.panelOpened && this.thyShowSearch) {
4!
72
            Promise.resolve(null).then(() => {
4✔
73
                this.inputElement.nativeElement.focus();
74
            });
75
        }
76
        if (!this.panelOpened && this.thyShowSearch) {
77
            Promise.resolve(null).then(() => {
78
                this.setInputValue('');
669✔
79
            });
80
        }
81
        this.setSelectControlClass();
292✔
82
    }
292✔
83

84
    @Input({ transform: coerceBooleanProperty })
85
    get thyIsMultiple(): boolean {
7,668✔
86
        return this.isMultiple;
87
    }
88

288✔
89
    set thyIsMultiple(value: boolean) {
288✔
90
        this.isMultiple = value;
288✔
91
        this.setSelectControlClass();
3✔
92
    }
93

285✔
94
    @Input({ transform: coerceBooleanProperty })
1✔
95
    get thyShowSearch(): boolean {
96
        return this.showSearch;
97
    }
284✔
98

99
    set thyShowSearch(value: boolean) {
100
        this.showSearch = value;
101
        this.setSelectControlClass();
66✔
102
    }
66✔
103

8✔
104
    @Input()
5✔
105
    get thySelectedOptions(): SelectOptionBase | SelectOptionBase[] {
106
        return this.selectedOptions;
107
    }
3✔
108

109
    set thySelectedOptions(value: SelectOptionBase | SelectOptionBase[]) {
110
        let sameValue = false;
111
        const oldValue = this.selectedOptions;
58✔
112
        if (this.isMultiple) {
113
            if (oldValue instanceof Array && value instanceof Array && oldValue.length === value.length) {
66✔
114
                sameValue = value.every((option, index) => option.thyValue === oldValue[index].thyValue);
115
            }
116
        } else {
532✔
117
            if (oldValue && value) {
532✔
118
                sameValue = (oldValue as SelectOptionBase).thyValue === (value as SelectOptionBase).thyValue;
1✔
119
            }
120
        }
532✔
121
        this.selectedOptions = value;
255✔
122
        if (this.panelOpened && this.thyShowSearch) {
123
            if (!sameValue) {
532✔
124
                Promise.resolve(null).then(() => {
19✔
125
                    this.setInputValue('');
126
                });
532✔
127
            }
128
            //等待组件渲染好再聚焦
129
            setTimeout(() => {
183✔
130
                if (this.panelOpened) {
131
                    this.inputElement.nativeElement.focus();
132
                }
2✔
133
            }, 200);
134
        }
135
    }
191✔
136

1✔
137
    @Input({ transform: coerceBooleanProperty })
138
    get thyDisabled(): boolean {
190✔
139
        return this.disabled;
140
    }
141

669✔
142
    set thyDisabled(value: boolean) {
143
        this.disabled = value;
144
        this.setSelectControlClass();
1,979✔
145
    }
146

147
    @Input()
148
    customDisplayTemplate: TemplateRef<any>;
284✔
149

284✔
150
    @Input({ transform: coerceBooleanProperty })
284✔
151
    thyAllowClear = false;
284✔
152

284✔
153
    @Input()
284✔
154
    thyPlaceholder = '';
284✔
155

284✔
156
    @Input()
284✔
157
    get thySize(): SelectControlSize {
284✔
158
        return this.size;
284✔
159
    }
284✔
160

284✔
161
    set thySize(value: SelectControlSize) {
284✔
162
        this.size = value;
284✔
163
        this.setSelectControlClass();
284✔
164

284✔
165
        if (value === 'sm') {
166
            this.tagSize = 'sm';
167
        } else if (value === 'lg') {
283✔
168
            this.tagSize = 'lg';
169
        } else {
170
            this.tagSize = 'md';
1,917✔
171
        }
1,917✔
172
    }
173

174
    @Input({ transform: numberAttribute }) thyMaxTagCount = 0;
175

176
    @Input({ transform: coerceBooleanProperty }) thyBorderless = false;
177

178
    @Input() thyPreset: string = '';
179

180
    @Output()
181
    thyOnSearch = new EventEmitter<string>();
1,917✔
182

1,917✔
183
    @Output()
184
    public thyOnRemove = new EventEmitter<{ item: SelectOptionBase; $eventOrigin: Event }>();
185

186
    @Output()
187
    public thyOnClear = new EventEmitter<Event>();
188

189
    @Output()
190
    public thyOnBlur = new EventEmitter<Event>();
27✔
191

21✔
192
    @ViewChild('inputElement')
21✔
193
    inputElement: ElementRef;
21✔
194

195
    get selectedValueStyle(): { [key: string]: string } {
196
        let showSelectedValue = false;
197
        if (this.showSearch) {
×
UNCOV
198
            if (this.panelOpened) {
×
199
                showSelectedValue = !(this.isComposing || this.inputValue);
200
            } else {
×
201
                showSelectedValue = true;
×
UNCOV
202
            }
×
203
        } else {
204
            showSelectedValue = true;
205
        }
206
        return { display: showSelectedValue ? 'flex' : 'none' };
207
    }
39✔
208

2!
209
    get placeholderStyle(): { [key: string]: string } {
2✔
210
        let placeholder = true;
211
        if (this.isSelectedValue) {
UNCOV
212
            placeholder = false;
×
213
        }
214
        if (!this.thyPlaceholder) {
215
            placeholder = false;
216
        }
217
        if (this.isComposing || this.inputValue) {
6✔
218
            placeholder = false;
219
        }
220
        return { display: placeholder ? 'block' : 'none' };
6✔
221
    }
222

223
    get selectedValue(): any {
123✔
224
        return this.thySelectedOptions;
225
    }
226

1✔
227
    get multipleSelectedValue(): any {
228
        return this.thySelectedOptions;
1✔
229
    }
230

231
    get maxSelectedTags() {
1✔
232
        if (this.thyMaxTagCount > 0 && this.thySelectedOptions instanceof Array && this.thySelectedOptions.length > this.thyMaxTagCount) {
233
            return this.thySelectedOptions.slice(0, this.thyMaxTagCount - 1);
234
        }
235
        return this.thySelectedOptions as SelectOptionBase[];
236
    }
237

238
    get showClearIcon(): boolean {
239
        return this.thyAllowClear && this.isSelectedValue;
240
    }
241

242
    get isSelectedValue(): boolean {
243
        return (
244
            (!this.isMultiple && !isUndefinedOrNull(this.thySelectedOptions)) ||
245
            (this.isMultiple && (<SelectOptionBase[]>this.thySelectedOptions).length > 0)
246
        );
247
    }
248

249
    constructor(private renderer: Renderer2) {}
250

251
    ngOnInit() {
1✔
252
        this.setSelectControlClass();
253
    }
254

255
    setSelectControlClass() {
256
        const modeType = this.isMultiple ? 'multiple' : 'single';
257
        const selectControlClass = {
258
            [`form-control`]: true,
259
            [`form-control-${this.thySize}`]: !!this.thySize,
260
            [`form-control-custom`]: true,
261
            [`select-control`]: true,
262
            [`select-control-${modeType}`]: true,
263
            [`select-control-show-search`]: this.showSearch,
264
            [`panel-is-opened`]: this.panelOpened,
265
            [`disabled`]: this.disabled
266
        };
267
        this.hostRenderer.updateClassByMap(selectControlClass);
268
        this.searchInputControlClass = {
269
            [`form-control`]: true,
270
            [`form-control-${this.thySize}`]: !!this.thySize,
271
            [`search-input-field`]: true,
272
            [`hidden`]: !this.thyShowSearch
273
        };
274
    }
275

276
    setInputValue(value: string) {
277
        if (value !== this.inputValue) {
278
            this.inputValue = value;
279
            this.updateWidth();
280
            this.thyOnSearch.emit(this.inputValue);
281
        }
282
    }
283

284
    handleBackspace(event: Event) {
285
        if ((event as KeyboardEvent).isComposing) {
286
            return;
287
        }
288
        if (this.inputValue.length === 0 && this.selectedOptions instanceof Array) {
289
            if (this.selectedOptions.length > 0) {
290
                this.removeHandle(this.selectedOptions[this.selectedOptions.length - 1], event);
291
            }
292
        }
293
    }
294

295
    updateWidth() {
296
        if (this.isMultiple && this.thyShowSearch) {
297
            if (this.inputValue || this.isComposing) {
298
                this.renderer.setStyle(this.inputElement.nativeElement, 'width', `${this.inputElement.nativeElement.scrollWidth}px`);
299
            } else {
300
                this.renderer.removeStyle(this.inputElement.nativeElement, 'width');
301
            }
302
        }
303
    }
304

305
    removeHandle(item: SelectOptionBase, $event: Event) {
306
        this.thyOnRemove.emit({ item: item, $eventOrigin: $event });
307
    }
308

309
    clearHandle($event: Event) {
310
        this.thyOnClear.emit($event);
311
    }
312

313
    trackValue(_index: number, option: SelectOptionBase): any {
314
        return option.thyValue;
315
    }
316

317
    onBlur(event: Event) {
318
        this.thyOnBlur.emit(event);
319
    }
320
}
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