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

atinc / ngx-tethys / 0bbb2cec-209e-4d8a-b1b3-6bc54e05daa6

04 Sep 2023 08:40AM UTC coverage: 15.616% (-74.6%) from 90.2%
0bbb2cec-209e-4d8a-b1b3-6bc54e05daa6

Pull #2829

circleci

cmm-va
fix: add test
Pull Request #2829: fix: add tabIndex

300 of 6386 branches covered (0.0%)

Branch coverage included in aggregate %.

78 of 78 new or added lines in 26 files covered. (100.0%)

2849 of 13779 relevant lines covered (20.68%)

83.41 hits per line

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

5.79
/src/shared/select/select-control/select-control.component.ts
1
import { InputBoolean, InputNumber } from 'ngx-tethys/core';
2
import { ThyTagSize } from 'ngx-tethys/tag';
3
import { isUndefinedOrNull } from 'ngx-tethys/util';
4

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

19
import { NgClass, NgFor, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common';
×
20
import { FormsModule } from '@angular/forms';
×
21
import { ThyIconComponent } from 'ngx-tethys/icon';
×
22
import { ThyTagComponent } from 'ngx-tethys/tag';
×
23
import { SelectOptionBase } from '../../option/select-option-base';
24

25
export type SelectControlSize = 'sm' | 'md' | 'lg' | '';
×
26

×
27
/**
×
28
 * @private
29
 */
30
@Component({
×
31
    selector: 'thy-select-control,[thySelectControl]',
32
    templateUrl: './select-control.component.html',
33
    changeDetection: ChangeDetectionStrategy.OnPush,
×
34
    standalone: true,
35
    imports: [FormsModule, NgClass, NgIf, NgStyle, NgFor, ThyTagComponent, NgTemplateOutlet, ThyIconComponent],
36
    host: {
×
37
        '[class.select-control-borderless]': 'thyBorderless'
×
38
    }
39
})
40
export class ThySelectControlComponent implements OnInit {
×
41
    inputValue = '';
42

43
    isComposing = false;
×
44

×
45
    panelOpened = false;
46

47
    isMultiple = false;
×
48

49
    showSearch = false;
50

×
51
    disabled = false;
×
52

×
53
    size: SelectControlSize;
×
54

×
55
    selectedOptions: SelectOptionBase | SelectOptionBase[];
56

57
    searchInputControlClass: { [key: string]: boolean };
58

×
59
    tagSize: ThyTagSize;
×
60

61
    private hostRenderer = useHostRenderer();
62

×
63
    @Input()
×
64
    @InputBoolean()
×
65
    get thyPanelOpened(): boolean {
×
66
        return this.panelOpened;
×
67
    }
68

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

×
84
    @Input()
85
    @InputBoolean()
86
    get thyIsMultiple(): boolean {
×
87
        return this.isMultiple;
×
88
    }
×
89

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

95
    @Input()
×
96
    @InputBoolean()
97
    get thyShowSearch(): boolean {
98
        return this.showSearch;
99
    }
×
100

×
101
    set thyShowSearch(value: boolean) {
×
102
        this.showSearch = value;
×
103
        this.setSelectControlClass();
104
    }
105

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

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

×
137
    @Input()
138
    @InputBoolean()
139
    get thyDisabled(): boolean {
×
140
        return this.disabled;
141
    }
142

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

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

×
151
    @Input()
×
152
    @InputBoolean()
×
153
    thyAllowClear = false;
×
154

×
155
    @Input()
×
156
    thyPlaceholder = '';
×
157

×
158
    @Input()
×
159
    get thySize(): SelectControlSize {
×
160
        return this.size;
×
161
    }
×
162

163
    set thySize(value: SelectControlSize) {
164
        this.size = value;
×
165
        this.setSelectControlClass();
166

167
        if (value === 'sm') {
×
168
            this.tagSize = 'sm';
×
169
        } else if (value === 'lg') {
170
            this.tagSize = 'lg';
171
        } else {
172
            this.tagSize = 'md';
173
        }
174
    }
175

176
    @Input() @InputNumber() thyMaxTagCount = 0;
177

178
    @Input() @InputBoolean() thyBorderless = false;
×
179

×
180
    @Output()
181
    thyOnSearch = new EventEmitter<string>();
182

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>();
×
191

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

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

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

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

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

231
    get maxSelectedTags() {
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
    }
1✔
248

249
    constructor(private renderer: Renderer2) {}
250

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

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

1✔
276
    setInputValue(value: string) {
277
        if (value !== this.inputValue) {
278
            this.inputValue = value;
279
            this.updateWidth();
1✔
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