• 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

1.57
/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,
UNCOV
16
    inject
×
UNCOV
17
} from '@angular/core';
×
UNCOV
18
import { useHostRenderer } from '@tethys/cdk/dom';
×
UNCOV
19

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

×
UNCOV
27
export type SelectControlSize = 'xs' | 'sm' | 'md' | 'lg' | '';
×
UNCOV
28

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

UNCOV
44
    inputValue = '';
×
UNCOV
45

×
UNCOV
46
    isComposing = false;
×
UNCOV
47

×
UNCOV
48
    panelOpened = false;
×
49

50
    isMultiple = false;
UNCOV
51

×
52
    showSearch = false;
53

54
    disabled = false;
×
55

56
    size: SelectControlSize;
UNCOV
57

×
UNCOV
58
    selectedOptions: SelectOptionBase | SelectOptionBase[];
×
59

60
    searchInputControlClass: { [key: string]: boolean };
UNCOV
61

×
62
    tagSize: ThyTagSize;
63

UNCOV
64
    private hostRenderer = useHostRenderer();
×
UNCOV
65

×
66
    @Input({ transform: coerceBooleanProperty })
67
    get thyPanelOpened(): boolean {
UNCOV
68
        return this.panelOpened;
×
69
    }
70

UNCOV
71
    set thyPanelOpened(value: boolean) {
×
UNCOV
72
        this.panelOpened = value;
×
UNCOV
73
        if (this.panelOpened && this.thyShowSearch) {
×
UNCOV
74
            Promise.resolve(null).then(() => {
×
UNCOV
75
                this.inputElement.nativeElement.focus();
×
76
            });
77
        }
78
        if (!this.panelOpened && this.thyShowSearch) {
UNCOV
79
            new Promise(resolve => setTimeout(resolve, 100)).then(() => {
×
UNCOV
80
                this.inputValue = '';
×
81
                this.updateWidth();
82
                this.thyOnSearch.emit(this.inputValue);
UNCOV
83
            });
×
UNCOV
84
        }
×
UNCOV
85
        this.setSelectControlClass();
×
UNCOV
86
    }
×
UNCOV
87

×
UNCOV
88
    @Input({ transform: coerceBooleanProperty })
×
89
    get thyIsMultiple(): boolean {
90
        return this.isMultiple;
91
    }
UNCOV
92

×
UNCOV
93
    set thyIsMultiple(value: boolean) {
×
UNCOV
94
        this.isMultiple = value;
×
95
        this.setSelectControlClass();
96
    }
97

98
    @Input({ transform: coerceBooleanProperty })
99
    get thyShowSearch(): boolean {
UNCOV
100
        return this.showSearch;
×
101
    }
102

UNCOV
103
    set thyShowSearch(value: boolean) {
×
UNCOV
104
        this.showSearch = value;
×
105
        this.setSelectControlClass();
106
    }
UNCOV
107

×
108
    @Input()
109
    get thySelectedOptions(): SelectOptionBase | SelectOptionBase[] {
UNCOV
110
        return this.selectedOptions;
×
UNCOV
111
    }
×
UNCOV
112

×
UNCOV
113
    set thySelectedOptions(value: SelectOptionBase | SelectOptionBase[]) {
×
114
        let sameValue = false;
UNCOV
115
        const oldValue = this.selectedOptions;
×
UNCOV
116
        if (this.isMultiple) {
×
117
            if (oldValue instanceof Array && value instanceof Array && oldValue.length === value.length) {
118
                sameValue = value.every((option, index) => option.thyValue === oldValue[index].thyValue);
UNCOV
119
            }
×
120
        } else {
121
            if (oldValue && value) {
122
                sameValue = (oldValue as SelectOptionBase).thyValue === (value as SelectOptionBase).thyValue;
UNCOV
123
            }
×
UNCOV
124
        }
×
UNCOV
125
        this.selectedOptions = value;
×
UNCOV
126
        if (this.panelOpened && this.thyShowSearch) {
×
127
            if (!sameValue) {
128
                Promise.resolve(null).then(() => {
UNCOV
129
                    this.inputValue = '';
×
130
                    this.updateWidth();
131
                });
132
            }
UNCOV
133
            //等待组件渲染好再聚焦
×
134
            setTimeout(() => {
UNCOV
135
                if (this.panelOpened) {
×
136
                    this.inputElement.nativeElement.focus();
137
                }
UNCOV
138
            }, 200);
×
UNCOV
139
        }
×
UNCOV
140
    }
×
141

UNCOV
142
    @Input({ transform: coerceBooleanProperty })
×
UNCOV
143
    get thyDisabled(): boolean {
×
144
        return this.disabled;
UNCOV
145
    }
×
UNCOV
146

×
147
    set thyDisabled(value: boolean) {
UNCOV
148
        this.disabled = value;
×
149
        this.setSelectControlClass();
150
    }
UNCOV
151

×
152
    @Input()
153
    customDisplayTemplate: TemplateRef<any>;
UNCOV
154

×
155
    @Input({ transform: coerceBooleanProperty })
156
    thyAllowClear = false;
UNCOV
157

×
UNCOV
158
    @Input()
×
159
    thyPlaceholder = '';
UNCOV
160

×
161
    @Input()
162
    get thySize(): SelectControlSize {
UNCOV
163
        return this.size;
×
164
    }
165

UNCOV
166
    set thySize(value: SelectControlSize) {
×
167
        this.size = value;
168
        this.setSelectControlClass();
169

UNCOV
170
        if (value === 'xs' || value === 'sm') {
×
171
            this.tagSize = 'sm';
172
        } else if (value === 'lg') {
UNCOV
173
            this.tagSize = 'lg';
×
UNCOV
174
        } else {
×
175
            this.tagSize = 'md';
176
        }
177
    }
178

179
    @Input({ transform: numberAttribute }) thyMaxTagCount = 0;
180

181
    @Input({ transform: coerceBooleanProperty }) thyBorderless = false;
182

183
    @Input() thyPreset: string = '';
UNCOV
184

×
UNCOV
185
    @Output()
×
186
    thyOnSearch = new EventEmitter<string>();
187

188
    @Output()
189
    public thyOnRemove = new EventEmitter<{ item: SelectOptionBase; $eventOrigin: Event }>();
190

191
    @Output()
192
    public thyOnClear = new EventEmitter<Event>();
UNCOV
193

×
UNCOV
194
    @Output()
×
UNCOV
195
    public thyOnBlur = new EventEmitter<Event>();
×
UNCOV
196

×
197
    @ViewChild('inputElement')
198
    inputElement: ElementRef;
199

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

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

228
    get selectedValue(): any {
UNCOV
229
        return this.thySelectedOptions;
×
230
    }
231

1✔
232
    get multipleSelectedValue(): any {
233
        return this.thySelectedOptions;
234
    }
235

236
    get maxSelectedTags() {
237
        if (this.thyMaxTagCount > 0 && this.thySelectedOptions instanceof Array && this.thySelectedOptions.length > this.thyMaxTagCount) {
238
            return this.thySelectedOptions.slice(0, this.thyMaxTagCount - 1);
239
        }
240
        return this.thySelectedOptions as SelectOptionBase[];
241
    }
242

243
    get showClearIcon(): boolean {
244
        return this.thyAllowClear && this.isSelectedValue;
245
    }
246

247
    get isSelectedValue(): boolean {
248
        return (
249
            (!this.isMultiple && !isUndefinedOrNull(this.thySelectedOptions)) ||
250
            (this.isMultiple && (<SelectOptionBase[]>this.thySelectedOptions).length > 0)
251
        );
1✔
252
    }
253

254
    ngOnInit() {
255
        this.setSelectControlClass();
256
    }
257

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

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

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

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

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

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

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

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