• 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

89.84
/src/shared/select/select-control/select-control.component.ts
1
import { ThyTagSize } from 'ngx-tethys/tag';
2
import { 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
    booleanAttribute,
NEW
16
    numberAttribute
×
17
} from '@angular/core';
18
import { useHostRenderer } from '@tethys/cdk/dom';
19

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

1✔
27
export type SelectControlSize = 'sm' | 'md' | 'lg' | '';
1✔
28

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

284✔
45
    isComposing = false;
46

47
    panelOpened = false;
2,194✔
48

49
    isMultiple = false;
50

391✔
51
    showSearch = false;
391✔
52

391✔
53
    disabled = false;
60✔
54

5✔
55
    size: SelectControlSize;
56

57
    selectedOptions: SelectOptionBase | SelectOptionBase[];
58

331✔
59
    searchInputControlClass: { [key: string]: boolean };
6✔
60

61
    tagSize: ThyTagSize;
62

391✔
63
    private hostRenderer = useHostRenderer();
391✔
64

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

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

85
    @Input({ transform: booleanAttribute })
7,304✔
86
    get thyIsMultiple(): boolean {
87
        return this.isMultiple;
88
    }
275✔
89

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

1✔
95
    @Input({ transform: booleanAttribute })
96
    get thyShowSearch(): boolean {
97
        return this.showSearch;
271✔
98
    }
99

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

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

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

138
    @Input({ transform: booleanAttribute })
161✔
139
    get thyDisabled(): boolean {
140
        return this.disabled;
141
    }
623✔
142

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

148
    @Input()
271✔
149
    customDisplayTemplate: TemplateRef<any>;
271✔
150

271✔
151
    @Input({ transform: booleanAttribute })
271✔
152
    thyAllowClear = false;
271✔
153

271✔
154
    @Input()
271✔
155
    thyPlaceholder = '';
271✔
156

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

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

166
        if (value === 'sm') {
167
            this.tagSize = 'sm';
270✔
168
        } else if (value === 'lg') {
169
            this.tagSize = 'lg';
170
        } else {
1,826✔
171
            this.tagSize = 'md';
1,826✔
172
        }
173
    }
174

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

177
    @Input({ transform: booleanAttribute }) thyBorderless = false;
178

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

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

184
    @Output()
185
    public thyOnRemove = new EventEmitter<{ item: SelectOptionBase; $eventOrigin: Event }>();
186

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

190
    @Output()
21✔
191
    public thyOnBlur = new EventEmitter<Event>();
19✔
192

19✔
193
    @ViewChild('inputElement')
19✔
194
    inputElement: ElementRef;
195

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

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

98✔
224
    get selectedValue(): any {
225
        return this.thySelectedOptions;
226
    }
1✔
227

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

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

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

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

250
    constructor(private renderer: Renderer2) {}
251

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

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

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

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

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

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

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

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

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