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

IgniteUI / igniteui-angular / 6787096818

07 Nov 2023 04:02PM UTC coverage: 92.1% (+0.002%) from 92.098%
6787096818

push

github

web-flow
fix(combos): selection event consistent data items, value args (#13619)

Rename existing `newSelection` and `oldSelection` to `newValue` and
`oldValue` respectively. Add new args in their place that emit data
items consistently regardless of `valueKey`. For `IgxCombo` also
update `added` and `removed` collections to always contain data items

15267 of 17958 branches covered (0.0%)

6 of 6 new or added lines in 2 files covered. (100.0%)

16 existing lines in 2 files now uncovered.

26417 of 28683 relevant lines covered (92.1%)

30202.33 hits per line

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

94.98
/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
2✔
48
 * @igxTheme igx-combo-theme
49
 * @igxKeywords combobox, single combo selection
50
 * @igxGroup Grids & Lists
997✔
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.
4,977!
55
 * @example
187✔
56
 * ```html
57
 * <igx-simple-combo [itemsMaxHeight]="250" [data]="locationData"
58
 *  [displayKey]="'field'" [valueKey]="'field'"
59
 *  placeholder="Location" searchPlaceholder="Search...">
9,136✔
60
 * </igx-simple-combo>
61
 * ```
62
 */
187✔
63
@Component({
64
    selector: 'igx-simple-combo',
65
    templateUrl: 'simple-combo.component.html',
84✔
66
    providers: [
67
        IgxComboAPIService,
68
        { provide: IGX_COMBO_COMPONENT, useExisting: IgxSimpleComboComponent },
84✔
69
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxSimpleComboComponent, multi: true }
84✔
70
    ],
84✔
71
    standalone: true,
72
    imports: [IgxInputGroupComponent, IgxInputDirective, IgxTextSelectionDirective, NgIf, IgxSuffixDirective, NgTemplateOutlet, IgxIconComponent, IgxComboDropDownComponent, IgxDropDownItemNavigationDirective, IgxForOfDirective, IgxComboItemComponent, IgxComboAddItemComponent, IgxButtonDirective, IgxRippleDirective, IgxComboFilteringPipe, IgxComboGroupingPipe]
84✔
73
})
84✔
74
export class IgxSimpleComboComponent extends IgxComboBaseDirective implements ControlValueAccessor, AfterViewInit, DoCheck {
75
    /** @hidden @internal */
84✔
76
    @ViewChild(IgxComboDropDownComponent, { static: true })
84✔
77
    public dropdown: IgxComboDropDownComponent;
84✔
78

1,433✔
79
    /** @hidden @internal */
1,433✔
80
    @ViewChild(IgxComboAddItemComponent)
81
    public addItem: IgxComboAddItemComponent;
13✔
82

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

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

3✔
96
    /** @hidden @internal */
2✔
97
    public composing = false;
2✔
98

99
    private _updateInput = true;
1!
100

1✔
101
    // stores the last filtered value - move to common?
102
    private _internalFilter = '';
103

104
    private _collapsing = false;
105

106
    /** @hidden @internal */
107
    public get filteredData(): any[] | null {
108
        return this._filteredData;
109
    }
110
    /** @hidden @internal */
111
    public set filteredData(val: any[] | null) {
112
        this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val;
113
        this.checkMatch();
53!
114
    }
53✔
115

53✔
116
    /** @hidden @internal */
117
    public override get searchValue(): string {
118
        return this._searchValue;
119
    }
120
    public override set searchValue(val: string) {
121
        this._searchValue = val;
122
    }
123

124
    private get selectedItem(): any {
125
        return this.selectionService.get(this.id).values().next().value;
126
    }
127

2✔
128
    constructor(elementRef: ElementRef,
129
        cdr: ChangeDetectorRef,
130
        selectionService: IgxSelectionAPIService,
131
        comboAPI: IgxComboAPIService,
53✔
132
        _iconService: IgxIconService,
53✔
133
        private platformUtil: PlatformUtil,
53✔
134
        @Optional() @Inject(DisplayDensityToken) _displayDensityOptions: IDisplayDensityOptions,
53✔
135
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) _inputGroupType: IgxInputGroupType,
53✔
136
        @Optional() _injector: Injector) {
53✔
137
        super(elementRef, cdr, selectionService, comboAPI,
138
            _iconService, _displayDensityOptions, _inputGroupType, _injector);
139
        this.comboAPI.register(this);
140
    }
70✔
141

3!
142
    /** @hidden @internal */
3✔
143
    @HostListener('keydown.ArrowDown', ['$event'])
30!
144
    @HostListener('keydown.Alt.ArrowDown', ['$event'])
30!
UNCOV
145
    public onArrowDown(event: Event): void {
×
146
        if (this.collapsed) {
147
            event.preventDefault();
30✔
148
            event.stopPropagation();
149
            this.open();
3!
150
        } else {
151
            if (this.virtDir.igxForOf.length > 0 && !this.selectedItem) {
152
                this.dropdown.navigateNext();
UNCOV
153
                this.dropdownContainer.nativeElement.focus();
×
154
            } else if (this.allowCustomValues) {
155
                this.addItem?.element.nativeElement.focus();
156
            }
157
        }
70✔
158
    }
59!
UNCOV
159

×
160
    /**
161
     * Select a defined item
59✔
162
     *
59✔
163
     * @param item the item to be selected
59✔
164
     * ```typescript
37✔
165
     * this.combo.select("New York");
37✔
166
     * ```
167
     */
22✔
168
    public select(item: any): void {
169
        if (item !== undefined) {
70✔
170
            const newSelection = this.selectionService.add_items(this.id, item instanceof Array ? item : [item], true);
16✔
171
            this.setSelection(newSelection);
2✔
172
        }
173
    }
16✔
174

175
    /**
70✔
176
     * Deselect the currently selected item
24!
UNCOV
177
     *
×
178
     * @param item the items to be deselected
179
     * ```typescript
24✔
180
     * this.combo.deselect("New York");
23✔
181
     * ```
182
     */
183
    public deselect(): void {
1✔
184
        this.clearSelection();
1✔
185
    }
186

24✔
187
    /** @hidden @internal */
188
    public writeValue(value: any): void {
70✔
189
        const oldSelection = this.selection;
8✔
190
        this.selectionService.select_items(this.id, this.isValid(value) ? [value] : [], true);
191
        this.cdr.markForCheck();
192
        this._displayValue = this.createDisplayText(this.selection, oldSelection);
193
        this._value = this.valueKey ? this.selection.map(item => item[this.valueKey]) : this.selection;
70✔
194
        this.filterValue = this._internalFilter = this._displayValue?.toString();
63✔
195
    }
63✔
196

197
    /** @hidden @internal */
70✔
198
    public override ngAfterViewInit(): void {
199
        this.virtDir.contentSizeChange.pipe(takeUntil(this.destroy$)).subscribe(() => {
200
            if (this.selection.length > 0) {
201
                const index = this.virtDir.igxForOf.findIndex(e => {
265✔
202
                    let current = e? e[this.valueKey] : undefined;
10✔
203
                    if (this.valueKey === null || this.valueKey === undefined) {
10✔
204
                        current = e;
205
                    }
265✔
206
                    return current === this.selection[0];
207
                });
208
                if (!this.isRemote) {
209
                    // navigate to item only if we have local data
33✔
210
                    // as with remote data this will fiddle with igxFor's scroll handler
31✔
211
                    // and will trigger another chunk load which will break the visualization
212
                    this.dropdown.navigateItem(index);
33✔
213
                }
33✔
214
            }
6✔
215
        });
216
        this.dropdown.opening.pipe(takeUntil(this.destroy$)).subscribe((args) => {
33✔
217
            if (args.cancel) {
218
                return;
1✔
219
            }
1✔
220
            this._collapsing = false;
1✔
221
            const filtered = this.filteredData.find(this.findAllMatches);
222
            if (filtered === undefined || filtered === null) {
33✔
223
                this.filterValue = this.searchValue = this.comboInput.value;
2✔
224
                return;
225
            }
226
            this.filterValue = this.searchValue = '';
33✔
227
        });
17✔
228
        this.dropdown.opened.pipe(takeUntil(this.destroy$)).subscribe(() => {
229
            if (this.composing) {
33✔
230
                this.comboInput.focus();
33✔
231
            }
232
            this._internalFilter = this.comboInput.value;
233
        });
234
        this.dropdown.closing.pipe(takeUntil(this.destroy$)).subscribe((args) => {
3✔
235
            if (args.cancel) {
1✔
236
                return;
1✔
237
            }
238
            if (this.getEditElement() && !args.event) {
239
                this._collapsing = true;
240
            } else {
241
                this.clearOnBlur();
47✔
242
                this._onTouchedCallback();
4✔
243
            }
4!
UNCOV
244
            this.comboInput.focus();
×
245
        });
246
        this.dropdown.closed.pipe(takeUntil(this.destroy$)).subscribe(() => {
4✔
247
            this.filterValue = this._internalFilter = this.comboInput.value;
4✔
248
        });
4✔
249

4✔
250
        // in reactive form the control is not present initially
251
        // and sets the selection to an invalid value in writeValue method
4✔
252
        if (!this.isValid(this.selectedItem)) {
4✔
253
            this.selectionService.clear(this.id);
4✔
254
            this._displayValue = '';
255
        }
43✔
256

257
        super.ngAfterViewInit();
2✔
258
    }
2✔
259

260
    /** @hidden @internal */
43✔
261
    public override ngDoCheck(): void {
9✔
262
        if (this.data?.length && this.selection.length && !this._displayValue) {
9✔
263
            this._displayValue = this.createDisplayText(this.selection, []);
264
            this._value = this.valueKey ? this.selection.map(item => item[this.valueKey]) : this.selection;
43✔
265
        }
43✔
266
        super.ngDoCheck();
267
    }
268

269
    /** @hidden @internal */
27✔
270
    public override handleInputChange(event?: any): void {
1✔
271
        if (event !== undefined) {
1!
UNCOV
272
            this.filterValue = this._internalFilter = this.searchValue = typeof event === 'string' ? event : event.target.value;
×
273
        }
274
        this._onChangeCallback(this.searchValue);
1✔
275
        if (this.collapsed && this.comboInput.focused) {
276
            this.open();
277
        }
278
        if (!this.comboInput.value.trim() && this.selection.length) {
279
            // handle clearing of input by space
4!
UNCOV
280
            this.clearSelection();
×
UNCOV
281
            this._onChangeCallback(null);
×
UNCOV
282
            this.filterValue = '';
×
283
        }
284
        if (this.selection.length) {
4!
285
            this.selectionService.clear(this.id);
×
286
        }
287
        // when filtering the focused item should be the first item or the currently selected item
288
        if (!this.dropdown.focusedItem || this.dropdown.focusedItem.id !== this.dropdown.items[0].id) {
289
            this.dropdown.navigateFirst();
290
        }
23✔
291
        super.handleInputChange(event);
23✔
292
        this.composing = true;
293
    }
294

295
    /** @hidden @internal */
296
    public handleInputClick(): void {
297
        if (this.collapsed) {
15✔
298
            this.open();
11✔
299
            this.comboInput.focus();
300
        }
15✔
301
    }
302

303
    /** @hidden @internal */
304
    public override handleKeyDown(event: KeyboardEvent): void {
42✔
305
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
306
            const filtered = this.filteredData.find(this.findAllMatches);
307
            if (filtered === null || filtered === undefined) {
308
                return;
24✔
309
            }
310
            this.select(this.dropdown.focusedItem.itemID);
311
            event.preventDefault();
312
            event.stopPropagation();
6✔
313
            this.close();
1✔
314
            // manually trigger text selection as it will not be triggered during editing
315
            this.textSelection.trigger();
5✔
316
            this.filterValue = this.getElementVal(filtered);
5✔
317
            return;
2✔
318
        }
319
        if (event.key === this.platformUtil.KEYMAP.BACKSPACE
5✔
320
            || event.key === this.platformUtil.KEYMAP.DELETE) {
5✔
321
            this._updateInput = false;
5✔
322
            this.clearSelection(true);
5✔
323
        }
5✔
324
        if (!this.collapsed && event.key === this.platformUtil.KEYMAP.TAB) {
325
            this.clearOnBlur();
326
            this.close();
327
        }
16✔
328
        this.composing = false;
16✔
329
        super.handleKeyDown(event);
15✔
330
    }
331

16✔
332
    /** @hidden @internal */
333
    public handleKeyUp(event: KeyboardEvent): void {
334
        if (event.key === this.platformUtil.KEYMAP.ARROW_DOWN) {
335
            const firstItem = this.selectionService.first_item(this.id);
26✔
336
            this.dropdown.focusedItem = firstItem && this.filteredData.length > 0
26✔
337
                ? this.dropdown.items.find(i => i.itemID === firstItem)
26✔
338
                : this.dropdown.items[0];
26✔
339
            this.dropdownContainer.nativeElement.focus();
1✔
340
        }
341
    }
25✔
342

343
    /** @hidden @internal */
25✔
344
    public handleItemKeyDown(event: KeyboardEvent): void {
25✔
345
        if (event.key === this.platformUtil.KEYMAP.ARROW_UP && event.altKey) {
346
            this.close();
347
            this.comboInput.focus();
348
            return;
2!
349
        }
2✔
350
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
351
            this.comboInput.focus();
UNCOV
352
        }
×
353
    }
354

355
    /** @hidden @internal */
356
    public handleItemClick(): void {
357
        this.close();
5✔
358
        this.comboInput.focus();
5✔
359
    }
3✔
360

361
    /** @hidden @internal */
362
    public override onBlur(): void {
363
        // when clicking the toggle button to close the combo and immediately clicking outside of it
79✔
364
        // the collapsed state is not modified as the dropdown is still not closed
79!
365
        if (this.collapsed || this._collapsing) {
79✔
366
            this.clearOnBlur();
79✔
367
        }
79✔
368
        super.onBlur();
79✔
369
    }
370

371
    /** @hidden @internal */
372
    public onFocus(): void {
373
        this._internalFilter = this.comboInput.value || '';
374
    }
375

376
    /** @hidden @internal */
377
    public getEditElement(): HTMLElement {
79✔
378
        return this.comboInput.nativeElement;
62✔
379
    }
380

381
    /** @hidden @internal */
79✔
382
    public handleClear(event: Event): void {
78✔
383
        if (this.disabled) {
384
            return;
385
        }
78✔
386
        this.clearSelection(true);
78✔
387
        if(!this.collapsed){
78✔
388
            this.focusSearchInput(true);
78✔
389
        }
76!
390
        event.stopPropagation();
391

392
        this.comboInput.value = this.filterValue = this.searchValue = '';
393
        this.dropdown.focusedItem = null;
78✔
394
        this.composing = false;
78✔
395
        this.comboInput.focus();
396
    }
1!
397

1✔
398
    /** @hidden @internal */
399
    public handleOpened(): void {
400
        this.triggerCheck();
401
        if (!this.comboInput.focused) {
218✔
402
            this.dropdownContainer.nativeElement.focus();
26!
403
        }
26✔
404
        this.opened.emit({ owner: this });
405
    }
192✔
406

407
    /** @hidden @internal */
408
    public override handleClosing(e: IBaseCancelableBrowserEventArgs): void {
77✔
409
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
410
        this.closing.emit(args);
115✔
411
        e.cancel = args.cancel;
412
        if (e.cancel) {
413
            return;
26✔
414
        }
8✔
415

8✔
416
        this.composing = false;
417
        // explicitly update selection and trigger text selection so that we don't have to force CD
18✔
418
        this.textSelection.selected = true;
18✔
419
        this.textSelection.trigger();
18✔
420
    }
421

422
    /** @hidden @internal */
18✔
423
    public focusSearchInput(opening?: boolean): void {
45✔
424
        if (opening) {
45✔
425
            this.dropdownContainer.nativeElement.focus();
16✔
426
        } else {
427
            this.comboInput.nativeElement.focus();
428
        }
29✔
429
    }
430

431
    /** @hidden @internal */
432
    public override onClick(event: Event): void {
26✔
433
        super.onClick(event);
26✔
434
        if (this.comboInput.value.length === 0) {
1✔
435
            this.virtDir.scrollTo(0);
436
        }
26✔
437
    }
438

439
    protected findAllMatches = (element: any): boolean => {
21✔
440
        const value = this.displayKey ? element[this.displayKey] : element;
2!
441
        if (value === null || value === undefined || value === '') {
2✔
442
            // we can accept null, undefined and empty strings as empty display values
2✔
443
            return true;
1✔
444
        }
445
        const searchValue = this.searchValue || this.comboInput.value;
2✔
446
        return !!searchValue && value.toString().toLowerCase().includes(searchValue.toLowerCase());
447
    };
19✔
448

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

487
    protected createDisplayText(newSelection: any[], oldSelection: any[]): string {
488
        if (this.isRemote) {
489
            const selection = this.valueKey ? newSelection.map(item => item[this.valueKey]) : newSelection;
490
            return this.getRemoteSelection(selection, oldSelection);
491
        }
492

493
        if (this.displayKey !== null
494
            && this.displayKey !== undefined
495
            && newSelection.length > 0) {
496
            return newSelection.filter(e => e).map(e => e[this.displayKey])[0]?.toString() || '';
497
        }
498

499
        return newSelection[0]?.toString() || '';
500
    }
501

502
    protected override getRemoteSelection(newSelection: any[], oldSelection: any[]): string {
503
        if (!newSelection.length) {
504
            this.registerRemoteEntries(oldSelection, false);
505
            return '';
506
        }
507

508
        this.registerRemoteEntries(oldSelection, false);
509
        this.registerRemoteEntries(newSelection);
510
        return Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
511
    }
512

513
    /** Contains key-value pairs of the selected valueKeys and their resp. displayKeys */
514
    protected override registerRemoteEntries(ids: any[], add = true) {
515
        const selection = this.getValueDisplayPairs(ids)[0];
516

517
        if (add && selection) {
518
            this._remoteSelection[selection[this.valueKey]] = selection[this.displayKey].toString();
519
        } else {
520
            delete this._remoteSelection[ids[0]];
521
        }
522
    }
523

524
    private clearSelection(ignoreFilter?: boolean): void {
525
        let newSelection = this.selectionService.get_empty();
526
        if (this.filteredData.length !== this.data.length && !ignoreFilter) {
527
            newSelection = this.selectionService.delete_items(this.id, this.selectionService.get_all_ids(this.filteredData, this.valueKey));
528
        }
529
        this.setSelection(newSelection);
530
    }
531

532
    private clearOnBlur(): void {
533
        if (this.isRemote) {
534
            const searchValue = this.searchValue || this.comboInput.value;
535
            const remoteValue = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
536
            if (searchValue !== remoteValue) {
537
                this.clear();
538
            }
539
            return;
540
        }
541

542
        const filtered = this.filteredData.find(this.findMatch);
543
        // selecting null in primitive data returns undefined as the search text is '', but the item is null
544
        if (filtered === undefined && this.selectedItem !== null || !this.selection.length) {
545
            this.clear();
546
        }
547
    }
548

549
    private getElementVal(element: any): string {
550
        const elementVal = this.displayKey ? element[this.displayKey] : element;
551
        return String(elementVal);
552
    }
553

554
    private clear(): void {
555
        this.clearSelection(true);
556
        this.comboInput.value = this._internalFilter = this._displayValue = this.searchValue = '';
557
    }
558

559
    private isValid(value: any): boolean {
560
        return this.required
561
        ? value !== null && value !== '' && value !== undefined
562
        : value !== undefined;
563
    }
564
}
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