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

atinc / ngx-tethys / 72b1ae71-5bf2-488d-85fa-e5298804ce26

28 Nov 2023 07:09AM UTC coverage: 90.303%. Remained the same
72b1ae71-5bf2-488d-85fa-e5298804ce26

Pull #2923

circleci

smile1016
feat(cascader): multi-select support template
Pull Request #2923: feat(select):multi-select selected options support template #INFR-10631

5316 of 6547 branches covered (0.0%)

Branch coverage included in aggregate %.

13 of 14 new or added lines in 2 files covered. (92.86%)

40 existing lines in 3 files now uncovered.

13253 of 14016 relevant lines covered (94.56%)

975.86 hits per line

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

90.67
/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,
15
    ViewChild
1✔
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';
420✔
21
import { ThyIconComponent } from 'ngx-tethys/icon';
420✔
22
import { ThyTagComponent } from 'ngx-tethys/tag';
18✔
23
import { SelectOptionBase } from '../../option/select-option-base';
18✔
24
import { ThyGridModule } from 'ngx-tethys/grid';
25

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

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

44
    isComposing = false;
276✔
45

276✔
46
    panelOpened = false;
47

48
    isMultiple = false;
2,101✔
49

50
    showSearch = false;
51

375✔
52
    disabled = false;
375✔
53

375✔
54
    size: SelectControlSize;
56✔
55

3✔
56
    selectedOptions: SelectOptionBase | SelectOptionBase[];
57

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

319✔
60
    tagSize: ThyTagSize;
7✔
61

62
    private hostRenderer = useHostRenderer();
63

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

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

85
    @Input()
86
    @InputBoolean()
7,060✔
87
    get thyIsMultiple(): boolean {
88
        return this.isMultiple;
89
    }
266✔
90

266✔
91
    set thyIsMultiple(value: boolean) {
266✔
92
        this.isMultiple = value;
3✔
93
        this.setSelectControlClass();
94
    }
263✔
95

1✔
96
    @Input()
97
    @InputBoolean()
98
    get thyShowSearch(): boolean {
262✔
99
        return this.showSearch;
100
    }
101

102
    set thyShowSearch(value: boolean) {
58✔
103
        this.showSearch = value;
58✔
104
        this.setSelectControlClass();
5✔
105
    }
3✔
106

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

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

148✔
140
    @Input()
141
    @InputBoolean()
142
    get thyDisabled(): boolean {
598✔
143
        return this.disabled;
144
    }
145

1,779✔
146
    set thyDisabled(value: boolean) {
147
        this.disabled = value;
148
        this.setSelectControlClass();
149
    }
262✔
150

262✔
151
    @Input()
262✔
152
    customDisplayTemplate: TemplateRef<any>;
262✔
153

262✔
154
    @Input()
262✔
155
    selectedOptionCustomDisplayTemplate: TemplateRef<any>;
262✔
156

262✔
157
    @Input()
262✔
158
    @InputBoolean()
262✔
159
    thyAllowClear = false;
262✔
160

262✔
161
    @Input()
262✔
162
    thyPlaceholder = '';
262✔
163

262✔
164
    @Input()
262✔
165
    get thySize(): SelectControlSize {
166
        return this.size;
167
    }
261✔
168

169
    set thySize(value: SelectControlSize) {
170
        this.size = value;
1,765✔
171
        this.setSelectControlClass();
1,765✔
172

173
        if (value === 'sm') {
174
            this.tagSize = 'sm';
175
        } else if (value === 'lg') {
176
            this.tagSize = 'lg';
177
        } else {
178
            this.tagSize = 'md';
179
        }
180
    }
181

1,765✔
182
    @Input() @InputNumber() thyMaxTagCount = 0;
1,765✔
183

184
    @Input() @InputBoolean() thyBorderless = false;
185

186
    @Output()
187
    thyOnSearch = new EventEmitter<string>();
188

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

19✔
192
    @Output()
19✔
193
    public thyOnClear = new EventEmitter<Event>();
19✔
194

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

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

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

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

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

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

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

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

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

255
    constructor(private renderer: Renderer2) {}
256

1✔
257
    ngOnInit() {
258
        this.setSelectControlClass();
259
    }
260

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

282
    setInputValue(value: string) {
283
        if (value !== this.inputValue) {
1✔
284
            this.inputValue = value;
285
            this.updateWidth();
286
            this.thyOnSearch.emit(this.inputValue);
287
        }
288
    }
289

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

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

311
    removeHandle(item: SelectOptionBase, $event: Event) {
312
        this.thyOnRemove.emit({ item: item, $eventOrigin: $event });
313
    }
314

315
    clearHandle($event: Event) {
316
        this.thyOnClear.emit($event);
317
    }
318

319
    trackValue(_index: number, option: SelectOptionBase): any {
320
        return option.thyValue;
321
    }
322

323
    onBlur(event: Event) {
324
        this.thyOnBlur.emit(event);
325
    }
326
}
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