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

IgniteUI / igniteui-angular / 7021005102

28 Nov 2023 03:29PM UTC coverage: 91.842% (+0.004%) from 91.838%
7021005102

push

github

web-flow
Merge pull request #13681 from IgniteUI/17.0.x

Merging 17.0.x into master

12492 of 14556 branches covered (0.0%)

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

2 existing lines in 1 file now uncovered.

25466 of 27728 relevant lines covered (91.84%)

30482.1 hits per line

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

94.34
/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.ts
1
import { NgIf, NgTemplateOutlet } from '@angular/common';
2
import {
3
    AfterViewInit, ChangeDetectorRef, Component, DoCheck, ElementRef, EventEmitter, HostListener, Inject, Injector,
4
    Optional, Output, ViewChild
5
} from '@angular/core';
6
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
7
import { takeUntil } from 'rxjs/operators';
8

9
import { IgxComboAddItemComponent } from '../combo/combo-add-item.component';
10
import { IgxComboDropDownComponent } from '../combo/combo-dropdown.component';
11
import { IgxComboItemComponent } from '../combo/combo-item.component';
12
import { IgxComboAPIService } from '../combo/combo.api';
13
import { IgxComboBaseDirective, IGX_COMBO_COMPONENT } from '../combo/combo.common';
14
import { DisplayDensityToken, IDisplayDensityOptions } from '../core/density';
15
import { IgxSelectionAPIService } from '../core/selection';
16
import { CancelableEventArgs, IBaseCancelableBrowserEventArgs, IBaseEventArgs, PlatformUtil } from '../core/utils';
17
import { IgxButtonDirective } from '../directives/button/button.directive';
18
import { IgxForOfDirective } from '../directives/for-of/for_of.directive';
19
import { IgxRippleDirective } from '../directives/ripple/ripple.directive';
20
import { IgxTextSelectionDirective } from '../directives/text-selection/text-selection.directive';
21
import { IgxIconService } from '../icon/icon.service';
22
import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from '../input-group/public_api';
23
import { IgxComboFilteringPipe, IgxComboGroupingPipe } from '../combo/combo.pipes';
24
import { IgxDropDownItemNavigationDirective } from '../drop-down/drop-down-navigation.directive';
25
import { IgxIconComponent } from '../icon/icon.component';
26
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
27
import { IgxInputDirective } from '../directives/input/input.directive';
28
import { IgxInputGroupComponent } from '../input-group/input-group.component';
29

30
/** Emitted when an igx-simple-combo's selection is changing.  */
31
export interface ISimpleComboSelectionChangingEventArgs extends CancelableEventArgs, IBaseEventArgs {
32
    /** An object which represents the value that is currently selected */
33
    oldValue: any;
34
    /** An object which represents the value that will be selected after this event */
35
    newValue: any;
36
    /** An object which represents the item that is currently selected */
37
    oldSelection: any;
38
    /** An object which represents the item that will be selected after this event */
39
    newSelection: any;
40
    /** The text that will be displayed in the combo text box */
41
    displayText: string;
42
}
43

44
/**
45
 * Represents a drop-down list that provides filtering functionality, allowing users to choose a single option from a predefined list.
46
 *
47
 * @igxModule IgxSimpleComboModule
48
 * @igxTheme igx-combo-theme
49
 * @igxKeywords combobox, single combo selection
50
 * @igxGroup Grids & Lists
51
 *
52
 * @remarks
53
 * It provides the ability to filter items as well as perform single selection on the provided data.
54
 * Additionally, it exposes keyboard navigation and custom styling capabilities.
55
 * @example
56
 * ```html
57
 * <igx-simple-combo [itemsMaxHeight]="250" [data]="locationData"
58
 *  [displayKey]="'field'" [valueKey]="'field'"
59
 *  placeholder="Location" searchPlaceholder="Search...">
60
 * </igx-simple-combo>
61
 * ```
62
 */
63
@Component({
64
    selector: 'igx-simple-combo',
65
    templateUrl: 'simple-combo.component.html',
66
    providers: [
67
        IgxComboAPIService,
68
        { provide: IGX_COMBO_COMPONENT, useExisting: IgxSimpleComboComponent },
69
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxSimpleComboComponent, multi: true }
70
    ],
71
    standalone: true,
72
    imports: [IgxInputGroupComponent, IgxInputDirective, IgxTextSelectionDirective, NgIf, IgxSuffixDirective, NgTemplateOutlet, IgxIconComponent, IgxComboDropDownComponent, IgxDropDownItemNavigationDirective, IgxForOfDirective, IgxComboItemComponent, IgxComboAddItemComponent, IgxButtonDirective, IgxRippleDirective, IgxComboFilteringPipe, IgxComboGroupingPipe]
73
})
74
export class IgxSimpleComboComponent extends IgxComboBaseDirective implements ControlValueAccessor, AfterViewInit, DoCheck {
2✔
75
    /** @hidden @internal */
76
    @ViewChild(IgxComboDropDownComponent, { static: true })
77
    public dropdown: IgxComboDropDownComponent;
78

79
    /** @hidden @internal */
80
    @ViewChild(IgxComboAddItemComponent)
81
    public addItem: IgxComboAddItemComponent;
82

83
    /**
84
     * Emitted when item selection is changing, before the selection completes
85
     *
86
     * ```html
87
     * <igx-simple-combo (selectionChanging)='handleSelection()'></igx-simple-combo>
88
     * ```
89
     */
90
    @Output()
91
    public selectionChanging = new EventEmitter<ISimpleComboSelectionChangingEventArgs>();
84✔
92

93
    @ViewChild(IgxTextSelectionDirective, { static: true })
94
    private textSelection: IgxTextSelectionDirective;
95

96
    public override get value(): any {
97
        return this._value[0];
51✔
98
    }
99

100
    /**
101
     * Get current selection state
102
     *
103
     * @returns The selected item, if any
104
     * ```typescript
105
     * let mySelection = this.combo.selection;
106
     * ```
107
     */
108
    public override get selection(): any {
109
        return super.selection[0];
72✔
110
    }
111

112
    /** @hidden @internal */
113
    public composing = false;
84✔
114

115
    private _updateInput = true;
84✔
116

117
    private _collapsing = false;
84✔
118

119
    /** @hidden @internal */
120
    public get filteredData(): any[] | null {
121
        return this._filteredData;
995✔
122
    }
123
    /** @hidden @internal */
124
    public set filteredData(val: any[] | null) {
125
        this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val;
4,975!
126
        this.checkMatch();
182✔
127
    }
128

129
    /** @hidden @internal */
130
    public override get searchValue(): string {
131
        return this._searchValue;
9,145✔
132
    }
133
    public override set searchValue(val: string) {
134
        this._searchValue = val;
189✔
135
    }
136

137
    private get selectedItem(): any {
138
        return this.selectionService.get(this.id).values().next().value;
82✔
139
    }
140

141
    protected get hasSelectedItem(): boolean {
142
        return !!this.selectionService.get(this.id).size;
627✔
143
    }
144

145
    constructor(elementRef: ElementRef,
146
        cdr: ChangeDetectorRef,
147
        selectionService: IgxSelectionAPIService,
148
        comboAPI: IgxComboAPIService,
149
        _iconService: IgxIconService,
150
        private platformUtil: PlatformUtil,
84✔
151
        @Optional() @Inject(DisplayDensityToken) _displayDensityOptions: IDisplayDensityOptions,
152
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) _inputGroupType: IgxInputGroupType,
153
        @Optional() _injector: Injector) {
154
        super(elementRef, cdr, selectionService, comboAPI,
84✔
155
            _iconService, _displayDensityOptions, _inputGroupType, _injector);
156
        this.comboAPI.register(this);
84✔
157
    }
158

159
    /** @hidden @internal */
160
    @HostListener('keydown.ArrowDown', ['$event'])
161
    @HostListener('keydown.Alt.ArrowDown', ['$event'])
162
    public onArrowDown(event: Event): void {
163
        if (this.collapsed) {
5✔
164
            event.preventDefault();
2✔
165
            event.stopPropagation();
2✔
166
            this.open();
2✔
167
        } else {
168
            if (this.virtDir.igxForOf.length > 0 && !this.hasSelectedItem) {
3✔
169
                this.dropdown.navigateNext();
2✔
170
                this.dropdownContainer.nativeElement.focus();
2✔
171
            } else if (this.allowCustomValues) {
1✔
172
                this.addItem?.element.nativeElement.focus();
1✔
173
            }
174
        }
175
    }
176

177
    /**
178
     * Select a defined item
179
     *
180
     * @param item the item to be selected
181
     * ```typescript
182
     * this.combo.select("New York");
183
     * ```
184
     */
185
    public select(item: any): void {
186
        if (item !== undefined) {
53✔
187
            const newSelection = this.selectionService.add_items(this.id, item instanceof Array ? item : [item], true);
53✔
188
            this.setSelection(newSelection);
53✔
189
        }
190
    }
191

192
    /**
193
     * Deselect the currently selected item
194
     *
195
     * @param item the items to be deselected
196
     * ```typescript
197
     * this.combo.deselect("New York");
198
     * ```
199
     */
200
    public deselect(): void {
201
        this.clearSelection();
2✔
202
    }
203

204
    /** @hidden @internal */
205
    public writeValue(value: any): void {
206
        const oldSelection = super.selection;
53✔
207
        this.selectionService.select_items(this.id, this.isValid(value) ? [value] : [], true);
53✔
208
        this.cdr.markForCheck();
53✔
209
        this._displayValue = this.createDisplayText(super.selection, oldSelection);
53✔
210
        this._value = this.valueKey ? super.selection.map(item => item[this.valueKey]) : super.selection;
53✔
211
        this.filterValue = this._displayValue?.toString() || '';
53✔
212
    }
213

214
    /** @hidden @internal */
215
    public override ngAfterViewInit(): void {
216
        this.virtDir.contentSizeChange.pipe(takeUntil(this.destroy$)).subscribe(() => {
70✔
217
            if (super.selection.length > 0) {
1✔
218
                const index = this.virtDir.igxForOf.findIndex(e => {
1✔
219
                    let current = e? e[this.valueKey] : undefined;
10!
220
                    if (this.valueKey === null || this.valueKey === undefined) {
10!
221
                        current = e;
×
222
                    }
223
                    return current === super.selection[0];
10✔
224
                });
225
                if (!this.isRemote) {
1!
226
                    // navigate to item only if we have local data
227
                    // as with remote data this will fiddle with igxFor's scroll handler
228
                    // and will trigger another chunk load which will break the visualization
229
                    this.dropdown.navigateItem(index);
×
230
                }
231
            }
232
        });
233
        this.dropdown.opening.pipe(takeUntil(this.destroy$)).subscribe((args) => {
70✔
234
            if (args.cancel) {
61!
235
                return;
×
236
            }
237
            this._collapsing = false;
61✔
238
            const filtered = this.filteredData.find(this.findAllMatches);
61✔
239
            if (filtered === undefined || filtered === null) {
61✔
240
                this.filterValue = this.searchValue = this.comboInput.value;
37✔
241
                return;
37✔
242
            }
243
            this.filterValue = this.searchValue = '';
24✔
244
        });
245
        this.dropdown.opened.pipe(takeUntil(this.destroy$)).subscribe(() => {
70✔
246
            if (this.composing) {
16✔
247
                this.comboInput.focus();
2✔
248
            }
249
        });
250
        this.dropdown.closing.pipe(takeUntil(this.destroy$)).subscribe((args) => {
70✔
251
            if (args.cancel) {
26!
252
                return;
×
253
            }
254
            if (this.getEditElement() && !args.event) {
26✔
255
                this._collapsing = true;
25✔
256
            } else {
257
                this.clearOnBlur();
1✔
258
                this._onTouchedCallback();
1✔
259
            }
260
            this.comboInput.focus();
26✔
261
        });
262

263
        // in reactive form the control is not present initially
264
        // and sets the selection to an invalid value in writeValue method
265
        if (!this.isValid(this.selectedItem)) {
70✔
266
            this.selectionService.clear(this.id);
63✔
267
            this._displayValue = '';
63✔
268
        }
269

270
        super.ngAfterViewInit();
70✔
271
    }
272

273
    /** @hidden @internal */
274
    public override ngDoCheck(): void {
275
        if (this.data?.length && super.selection.length && !this._displayValue) {
265✔
276
            this._displayValue = this.createDisplayText(super.selection, []);
10✔
277
            this._value = this.valueKey ? super.selection.map(item => item[this.valueKey]) : super.selection;
10✔
278
        }
279
        super.ngDoCheck();
265✔
280
    }
281

282
    /** @hidden @internal */
283
    public override handleInputChange(event?: any): void {
284
        if (event !== undefined) {
33✔
285
            this.filterValue = this.searchValue = typeof event === 'string' ? event : event.target.value;
31✔
286
        }
287
        this._onChangeCallback(this.searchValue);
33✔
288
        if (this.collapsed && this.comboInput.focused) {
33✔
289
            this.open();
8✔
290
        }
291
        if (!this.comboInput.value.trim() && super.selection.length) {
33✔
292
            // handle clearing of input by space
293
            this.clearSelection();
1✔
294
            this._onChangeCallback(null);
1✔
295
            this.filterValue = '';
1✔
296
        }
297
        if (super.selection.length) {
33✔
298
            this.selectionService.clear(this.id);
2✔
299
        }
300
        // when filtering the focused item should be the first item or the currently selected item
301
        if (!this.dropdown.focusedItem || this.dropdown.focusedItem.id !== this.dropdown.items[0].id) {
33✔
302
            this.dropdown.navigateFirst();
17✔
303
        }
304
        super.handleInputChange(event);
33✔
305
        this.composing = true;
33✔
306
    }
307

308
    /** @hidden @internal */
309
    public handleInputClick(): void {
310
        if (this.collapsed) {
3✔
311
            this.open();
1✔
312
            this.comboInput.focus();
1✔
313
        }
314
    }
315

316
    /** @hidden @internal */
317
    public override handleKeyDown(event: KeyboardEvent): void {
318
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
47✔
319
            const filtered = this.filteredData.find(this.findAllMatches);
4✔
320
            if (filtered === null || filtered === undefined) {
4!
321
                return;
×
322
            }
323
            this.select(this.dropdown.focusedItem.itemID);
4✔
324
            event.preventDefault();
4✔
325
            event.stopPropagation();
4✔
326
            this.close();
4✔
327
            // manually trigger text selection as it will not be triggered during editing
328
            this.textSelection.trigger();
4✔
329
            return;
4✔
330
        }
331
        if (event.key === this.platformUtil.KEYMAP.BACKSPACE
43✔
332
            || event.key === this.platformUtil.KEYMAP.DELETE) {
333
            this._updateInput = false;
2✔
334
            this.clearSelection(true);
2✔
335
        }
336
        if (!this.collapsed && event.key === this.platformUtil.KEYMAP.TAB) {
43✔
337
            this.clearOnBlur();
10✔
338
            this.close();
10✔
339
        }
340
        this.composing = false;
43✔
341
        super.handleKeyDown(event);
43✔
342
    }
343

344
    /** @hidden @internal */
345
    public handleKeyUp(event: KeyboardEvent): void {
346
        if (event.key === this.platformUtil.KEYMAP.ARROW_DOWN) {
27✔
347
            this.dropdown.focusedItem = this.hasSelectedItem && this.filteredData.length > 0
1!
348
                ? this.dropdown.items.find(i => i.itemID === this.selectedItem)
×
349
                : this.dropdown.items[0];
350
            this.dropdownContainer.nativeElement.focus();
1✔
351
        }
352
    }
353

354
    /** @hidden @internal */
355
    public handleItemKeyDown(event: KeyboardEvent): void {
356
        if (event.key === this.platformUtil.KEYMAP.ARROW_UP && event.altKey) {
4!
357
            this.close();
×
358
            this.comboInput.focus();
×
359
            return;
×
360
        }
361
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
4!
362
            this.comboInput.focus();
×
363
        }
364
    }
365

366
    /** @hidden @internal */
367
    public handleItemClick(): void {
368
        this.close();
23✔
369
        this.comboInput.focus();
23✔
370
    }
371

372
    /** @hidden @internal */
373
    public override onBlur(): void {
374
        // when clicking the toggle button to close the combo and immediately clicking outside of it
375
        // the collapsed state is not modified as the dropdown is still not closed
376
        if (this.collapsed || this._collapsing) {
15✔
377
            this.clearOnBlur();
11✔
378
        }
379
        super.onBlur();
15✔
380
    }
381

382
    /** @hidden @internal */
383
    public getEditElement(): HTMLElement {
384
        return this.comboInput.nativeElement;
26✔
385
    }
386

387
    /** @hidden @internal */
388
    public handleClear(event: Event): void {
389
        if (this.disabled) {
6✔
390
            return;
1✔
391
        }
392
        this.clearSelection(true);
5✔
393
        if(!this.collapsed){
5✔
394
            this.focusSearchInput(true);
2✔
395
        }
396
        event.stopPropagation();
5✔
397

398
        this.comboInput.value = this.filterValue = this.searchValue = '';
5✔
399
        this.dropdown.focusedItem = null;
5✔
400
        this.composing = false;
5✔
401
        this.comboInput.focus();
5✔
402
    }
403

404
    /** @hidden @internal */
405
    public handleOpened(): void {
406
        this.triggerCheck();
16✔
407
        if (!this.comboInput.focused) {
16✔
408
            this.dropdownContainer.nativeElement.focus();
15✔
409
        }
410
        this.opened.emit({ owner: this });
16✔
411
    }
412

413
    /** @hidden @internal */
414
    public override handleClosing(e: IBaseCancelableBrowserEventArgs): void {
415
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
28✔
416
        this.closing.emit(args);
28✔
417
        e.cancel = args.cancel;
28✔
418
        if (e.cancel) {
28✔
419
            return;
1✔
420
        }
421

422
        this.composing = false;
27✔
423
        // explicitly update selection and trigger text selection so that we don't have to force CD
424
        this.textSelection.selected = true;
27✔
425
        this.textSelection.trigger();
27✔
426
    }
427

428
    /** @hidden @internal */
429
    public focusSearchInput(opening?: boolean): void {
430
        if (opening) {
4✔
431
            this.dropdownContainer.nativeElement.focus();
2✔
432
        } else {
433
            this.comboInput.nativeElement.focus();
2✔
434
        }
435
    }
436

437
    /** @hidden @internal */
438
    public override onClick(event: Event): void {
439
        super.onClick(event);
5✔
440
        if (this.comboInput.value.length === 0) {
5✔
441
            this.virtDir.scrollTo(0);
3✔
442
        }
443
    }
444

445
    protected findAllMatches = (element: any): boolean => {
84✔
446
        const value = this.displayKey ? element[this.displayKey] : element;
1,435✔
447
        if (value === null || value === undefined || value === '') {
1,435✔
448
            // we can accept null, undefined and empty strings as empty display values
449
            return true;
13✔
450
        }
451
        const searchValue = this.searchValue || this.comboInput.value;
1,422✔
452
        return !!searchValue && value.toString().toLowerCase().includes(searchValue.toLowerCase());
1,422✔
453
    };
454

455
    protected setSelection(newSelection: any): void {
456
        const newValueAsArray = newSelection ? Array.from(newSelection) as IgxComboItemComponent[] : [];
79✔
457
        const oldValueAsArray = Array.from(this.selectionService.get(this.id) || []);
79!
458
        const newItems = this.convertKeysToItems(newValueAsArray);
79✔
459
        const oldItems = this.convertKeysToItems(oldValueAsArray);
79✔
460
        const displayText = this.createDisplayText(this.convertKeysToItems(newValueAsArray), oldValueAsArray);
79✔
461
        const args: ISimpleComboSelectionChangingEventArgs = {
79✔
462
            newValue: newValueAsArray[0],
463
            oldValue: oldValueAsArray[0],
464
            newSelection: newItems[0],
465
            oldSelection: oldItems[0],
466
            displayText,
467
            owner: this,
468
            cancel: false
469
        };
470
        if (args.newSelection !== args.oldSelection) {
79✔
471
            this.selectionChanging.emit(args);
62✔
472
        }
473
        // TODO: refactor below code as it sets the selection and the display text
474
        if (!args.cancel) {
79✔
475
            let argsSelection = this.isValid(args.newValue)
78✔
476
                ? args.newValue
477
                : [];
478
            argsSelection = Array.isArray(argsSelection) ? argsSelection : [argsSelection];
78✔
479
            this.selectionService.select_items(this.id, argsSelection, true);
78✔
480
            this._value = argsSelection;
78✔
481
            if (this._updateInput) {
78✔
482
                this.comboInput.value = this._displayValue = this.searchValue = displayText !== args.displayText
76!
483
                    ? args.displayText
484
                    : this.createDisplayText(super.selection, [args.oldValue]);
485
            }
486
            this._onChangeCallback(args.newValue);
78✔
487
            this._updateInput = true;
78✔
488
        } else if (this.isRemote) {
1✔
489
            this.registerRemoteEntries(newValueAsArray, false);
1✔
490
        }
491
    }
492

493
    protected createDisplayText(newSelection: any[], oldSelection: any[]): string {
494
        if (this.isRemote) {
218✔
495
            const selection = this.valueKey ? newSelection.map(item => item[this.valueKey]) : newSelection;
26!
496
            return this.getRemoteSelection(selection, oldSelection);
26✔
497
        }
498

499
        if (this.displayKey !== null
192✔
500
            && this.displayKey !== undefined
501
            && newSelection.length > 0) {
502
            return newSelection.filter(e => e).map(e => e[this.displayKey])[0]?.toString() || '';
77✔
503
        }
504

505
        return newSelection[0]?.toString() || '';
115✔
506
    }
507

508
    protected override getRemoteSelection(newSelection: any[], oldSelection: any[]): string {
509
        if (!newSelection.length) {
26✔
510
            this.registerRemoteEntries(oldSelection, false);
8✔
511
            return '';
8✔
512
        }
513

514
        this.registerRemoteEntries(oldSelection, false);
18✔
515
        this.registerRemoteEntries(newSelection);
18✔
516
        return Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
18✔
517
    }
518

519
    /** Contains key-value pairs of the selected valueKeys and their resp. displayKeys */
520
    protected override registerRemoteEntries(ids: any[], add = true) {
18✔
521
        const selection = this.getValueDisplayPairs(ids)[0];
45✔
522

523
        if (add && selection) {
45✔
524
            this._remoteSelection[selection[this.valueKey]] = selection[this.displayKey].toString();
16✔
525
        } else {
526
            delete this._remoteSelection[ids[0]];
29✔
527
        }
528
    }
529

530
    private clearSelection(ignoreFilter?: boolean): void {
531
        let newSelection = this.selectionService.get_empty();
26✔
532
        if (this.filteredData.length !== this.data.length && !ignoreFilter) {
26✔
533
            newSelection = this.selectionService.delete_items(this.id, this.selectionService.get_all_ids(this.filteredData, this.valueKey));
1✔
534
        }
535
        this.setSelection(newSelection);
26✔
536
    }
537

538
    private clearOnBlur(): void {
539
        if (this.isRemote) {
22✔
540
            const searchValue = this.searchValue || this.comboInput.value;
2!
541
            const remoteValue = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
2✔
542
            if (searchValue !== remoteValue) {
2✔
543
                this.clear();
1✔
544
            }
545
            return;
2✔
546
        }
547

548
        const filtered = this.filteredData.find(this.findMatch);
20✔
549
        // selecting null in primitive data returns undefined as the search text is '', but the item is null
550
        if (filtered === undefined && this.selectedItem !== null || !super.selection.length) {
20✔
551
            this.clear();
15✔
552
        }
553
    }
554

555
    private getElementVal(element: any): string {
UNCOV
556
        const elementVal = this.displayKey ? element[this.displayKey] : element;
×
UNCOV
557
        return String(elementVal);
×
558
    }
559

560
    private clear(): void {
561
        this.clearSelection(true);
16✔
562
        this.comboInput.value = this._displayValue = this.searchValue = '';
16✔
563
    }
564

565
    private isValid(value: any): boolean {
566
        return this.required
201✔
567
        ? value !== null && value !== '' && value !== undefined
89✔
568
        : value !== undefined;
569
    }
570
}
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

© 2026 Coveralls, Inc