Skip to content

Commit 30443f2

Browse files
authored
Merge pull request #10467 from IgniteUI/bpenkov/simple-combo-clear
Clear selection on blur when accessing non-existent item
2 parents f91a32f + 847bb53 commit 30443f2

File tree

5 files changed

+155
-55
lines changed

5 files changed

+155
-55
lines changed

projects/igniteui-angular/src/lib/combo/combo.common.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { IgxSelectionAPIService } from '../core/selection';
1111
import { CancelableBrowserEventArgs, cloneArray, IBaseCancelableBrowserEventArgs, IBaseEventArgs } from '../core/utils';
1212
import { SortingDirection } from '../data-operations/sorting-expression.interface';
1313
import { IForOfState, IgxForOfDirective } from '../directives/for-of/for_of.directive';
14-
import { ISelectionEventArgs } from '../drop-down/public_api';
1514
import { IgxIconService } from '../icon/public_api';
1615
import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from '../input-group/inputGroupType';
1716
import { IgxInputDirective, IgxInputGroupComponent, IgxInputState } from '../input-group/public_api';
@@ -23,8 +22,7 @@ import {
2322
IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboHeaderItemDirective, IgxComboItemDirective, IgxComboToggleIconDirective
2423
} from './combo.directives';
2524
import {
26-
IComboFilteringOptions, IComboItemAdditionEvent, IComboSearchInputEventArgs,
27-
IComboSelectionChangingEventArgs
25+
IComboFilteringOptions, IComboItemAdditionEvent, IComboSearchInputEventArgs
2826
} from './public_api';
2927

3028
export const IGX_COMBO_COMPONENT = new InjectionToken<IgxComboBase>('IgxComboComponentToken');
@@ -100,6 +98,22 @@ export enum IgxComboState {
10098
@Directive()
10199
export abstract class IgxComboBaseDirective extends DisplayDensityBase implements IgxComboBase, OnInit, DoCheck,
102100
AfterViewInit, OnDestroy, ControlValueAccessor {
101+
/**
102+
* Defines whether the caseSensitive icon should be shown in the search input
103+
*
104+
* ```typescript
105+
* // get
106+
* let myComboShowSearchCaseIcon = this.combo.showSearchCaseIcon;
107+
* ```
108+
*
109+
* ```html
110+
* <!--set-->
111+
* <igx-combo [showSearchCaseIcon]='true'></igx-combo>
112+
* ```
113+
*/
114+
@Input()
115+
public showSearchCaseIcon = false;
116+
103117
/**
104118
* Set custom overlay settings that control how the combo's list of items is displayed.
105119
* Set:
@@ -414,17 +428,6 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
414428
this._type = val;
415429
}
416430

417-
/**
418-
* Emitted when item selection is changing, before the selection completes
419-
*
420-
* ```html
421-
* <igx-combo (selectionChanging)='handleSelection()'></igx-combo>
422-
* ```
423-
*/
424-
// TODO: any for old/new selection?
425-
@Output()
426-
public selectionChanging = new EventEmitter<IComboSelectionChangingEventArgs | ISelectionEventArgs>();
427-
428431
/**
429432
* Emitted before the dropdown is opened
430433
*
@@ -865,6 +868,8 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
865868

866869
public abstract dropdown: IgxComboDropDownComponent;
867870

871+
public abstract selectionChanging: EventEmitter<any>;
872+
868873
constructor(
869874
protected elementRef: ElementRef,
870875
protected cdr: ChangeDetectorRef,
@@ -1125,6 +1130,11 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement
11251130
}
11261131
}
11271132

1133+
/** @hidden @internal */
1134+
public toggleCaseSensitive() {
1135+
this.filteringOptions = { caseSensitive: !this.filteringOptions.caseSensitive };
1136+
}
1137+
11281138
protected onStatusChanged = () => {
11291139
if ((this.ngControl.control.touched || this.ngControl.control.dirty) &&
11301140
(this.ngControl.control.validator || this.ngControl.control.asyncValidator)) {

projects/igniteui-angular/src/lib/combo/combo.component.ts

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CommonModule } from '@angular/common';
22
import {
3-
AfterViewInit, ChangeDetectorRef, Component, ElementRef, NgModule, OnInit, OnDestroy, Optional, Inject, Injector, ViewChild, Input
3+
AfterViewInit, ChangeDetectorRef, Component, ElementRef, NgModule, OnInit, OnDestroy,
4+
Optional, Inject, Injector, ViewChild, Input, Output, EventEmitter
45
} from '@angular/core';
56
import {
67
IgxComboItemDirective,
@@ -18,7 +19,7 @@ import { IgxSelectionAPIService } from '../core/selection';
1819
import { IBaseEventArgs, IBaseCancelableEventArgs, CancelableEventArgs } from '../core/utils';
1920
import { IgxStringFilteringOperand, IgxBooleanFilteringOperand } from '../data-operations/filtering-condition';
2021
import { FilteringLogic } from '../data-operations/filtering-expression.interface';
21-
import { IgxForOfModule, IForOfState } from '../directives/for-of/for_of.directive';
22+
import { IgxForOfModule } from '../directives/for-of/for_of.directive';
2223
import { IgxIconModule, IgxIconService } from '../icon/public_api';
2324
import { IgxRippleModule } from '../directives/ripple/ripple.directive';
2425
import { IgxToggleModule } from '../directives/toggle/toggle.directive';
@@ -114,22 +115,6 @@ const diffInSets = (set1: Set<any>, set2: Set<any>): any[] => {
114115
})
115116
export class IgxComboComponent extends IgxComboBaseDirective implements AfterViewInit, ControlValueAccessor, OnInit,
116117
OnDestroy, EditorProvider {
117-
/**
118-
* Defines whether the caseSensitive icon should be shown in the search input
119-
*
120-
* ```typescript
121-
* // get
122-
* let myComboShowSearchCaseIcon = this.combo.showSearchCaseIcon;
123-
* ```
124-
*
125-
* ```html
126-
* <!--set-->
127-
* <igx-combo [showSearchCaseIcon]='true'></igx-combo>
128-
* ```
129-
*/
130-
@Input()
131-
public showSearchCaseIcon = false;
132-
133118
/**
134119
* An @Input property that controls whether the combo's search box
135120
* should be focused after the `opened` event is called
@@ -163,6 +148,16 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
163148
@Input()
164149
public searchPlaceholder = 'Enter a Search Term';
165150

151+
/**
152+
* Emitted when item selection is changing, before the selection completes
153+
*
154+
* ```html
155+
* <igx-combo (selectionChanging)='handleSelection()'></igx-combo>
156+
* ```
157+
*/
158+
@Output()
159+
public selectionChanging = new EventEmitter<IComboSelectionChangingEventArgs>();
160+
166161
/** @hidden @internal */
167162
@ViewChild(IgxComboDropDownComponent, { static: true })
168163
public dropdown: IgxComboDropDownComponent;
@@ -369,13 +364,6 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie
369364
}
370365
}
371366

372-
/**
373-
* @hidden @internal
374-
*/
375-
public toggleCaseSensitive() {
376-
this.filteringOptions = { caseSensitive: !this.filteringOptions.caseSensitive };
377-
}
378-
379367
/** @hidden @internal */
380368
public handleOpened() {
381369
this.triggerCheck();

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<igx-input-group #inputGroup [displayDensity]="displayDensity" [type]="type">
1+
<igx-input-group #inputGroup [displayDensity]="displayDensity" [suppressInputAutofocus]="true" [type]="type">
22
<ng-container ngProjectAs="[igxLabel]">
33
<ng-content select="[igxLabel]"></ng-content>
44
</ng-container>
@@ -10,9 +10,9 @@
1010
</ng-container>
1111

1212
<input #comboInput igxInput [value]="value" (input)="handleInputChange($event)" (keyup)="handleKeyUp($event)"
13-
(keydown)="handleKeyDown($event)" (blur)="onBlur()" [attr.placeholder]="placeholder"
14-
aria-autocomplete="both" [attr.aria-owns]="dropdown.id" [attr.aria-labelledby]="ariaLabelledBy"
15-
[disabled]="disabled" [igxTextSelection]="!composing" />
13+
(keydown)="handleKeyDown($event)" (blur)="onBlur()" [attr.placeholder]="placeholder" aria-autocomplete="both"
14+
[attr.aria-owns]="dropdown.id" [attr.aria-labelledby]="ariaLabelledBy" [disabled]="disabled"
15+
[igxTextSelection]="!composing" />
1616

1717
<ng-container ngProjectAs="igx-suffix">
1818
<ng-content select="igx-suffix"></ng-content>
@@ -26,6 +26,11 @@
2626
clear
2727
</igx-icon>
2828
</igx-suffix>
29+
<igx-suffix *ngIf="showSearchCaseIcon">
30+
<igx-icon family="imx-icons" name="case-sensitive" [active]="filteringOptions.caseSensitive"
31+
(click)="toggleCaseSensitive()">
32+
</igx-icon>
33+
</igx-suffix>
2934
<igx-suffix class="igx-combo__toggle-button">
3035
<ng-container *ngIf="toggleIconTemplate">
3136
<ng-container *ngTemplateOutlet="toggleIconTemplate; context: {$implicit: collapsed}"></ng-container>
@@ -43,8 +48,9 @@
4348
</ng-container>
4449
<div #dropdownItemContainer class="igx-combo__content" [style.overflow]="'hidden'"
4550
[style.maxHeight.px]="itemsMaxHeight" [igxDropDownItemNavigation]="dropdown" (focus)="dropdown.onFocus()"
46-
[tabindex]="dropdown.collapsed ? -1 : 0" role="listbox" [attr.id]="dropdown.id" (keydown)="handleItemKeyDown($event)">
47-
<igx-combo-item role="option" [singleMode]="true" [itemHeight]='itemHeight' (click)="close()" *igxFor="let item of data
51+
[tabindex]="dropdown.collapsed ? -1 : 0" role="listbox" [attr.id]="dropdown.id"
52+
(keydown)="handleItemKeyDown($event)">
53+
<igx-combo-item role="option" [singleMode]="true" [itemHeight]='itemHeight' (click)="handleItemClick()" *igxFor="let item of data
4854
| comboFiltering:filterValue:displayKey:filteringOptions:true
4955
| comboGrouping:groupKey:valueKey:groupSortingDirection;
5056
index as rowIndex; containerSize: itemsMaxHeight; scrollOrientation: 'vertical'; itemSize: itemHeight"
@@ -68,7 +74,7 @@
6874
<ng-container *ngTemplateOutlet="emptyTemplate ? emptyTemplate : empty">
6975
</ng-container>
7076
</div>
71-
<igx-combo-add-item [itemHeight]="itemHeight" *ngIf="isAddButtonVisible()"
77+
<igx-combo-add-item #addItem [itemHeight]="itemHeight" *ngIf="isAddButtonVisible()"
7278
[tabindex]="dropdown.collapsed ? -1 : customValueFlag ? 1 : -1" class="igx-combo__add-item" role="button"
7379
aria-label="Add Item" [index]="virtualScrollContainer.igxForOf.length">
7480
<ng-container *ngTemplateOutlet="addItemTemplate ? addItemTemplate : addItemDefault">

projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const CSS_CLASS_INPUTGROUP_WRAPPER = 'igx-input-group__wrapper';
3131
const CSS_CLASS_INPUTGROUP_BUNDLE = 'igx-input-group__bundle';
3232
const CSS_CLASS_INPUTGROUP_MAINBUNDLE = 'igx-input-group__bundle-main';
3333
const CSS_CLASS_INPUTGROUP_BORDER = 'igx-input-group__border';
34+
const CSS_CLASS_ADDBUTTON = 'igx-combo__add-item';
3435
const CSS_CLASS_HEADER = 'header-class';
3536
const CSS_CLASS_FOOTER = 'footer-class';
3637
const defaultDropdownItemHeight = 40;
@@ -473,7 +474,7 @@ describe('IgxSimpleCombo', () => {
473474
tick();
474475
fixture.detectChanges();
475476
expect(combo.collapsed).toEqual(false);
476-
expect(combo.open).toHaveBeenCalledTimes(2);
477+
expect(combo.open).toHaveBeenCalledTimes(1);
477478

478479
combo.handleKeyDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowUp'));
479480
tick();
@@ -504,13 +505,48 @@ describe('IgxSimpleCombo', () => {
504505
expect(combo.closed.emit).not.toHaveBeenCalled();
505506
expect(combo.selection.length).toEqual(1);
506507
});
508+
509+
it('should clear the selection on tab/blur if the search text does not match any value', fakeAsync(() => {
510+
// allowCustomValues does not matter
511+
combo.select(combo.data[2][combo.valueKey]);
512+
fixture.detectChanges();
513+
expect(combo.selection.length).toBe(1);
514+
expect(input.nativeElement.value).toEqual('Massachusetts');
515+
516+
UIInteractions.setInputElementValue(input.nativeElement, 'MassachusettsL');
517+
fixture.detectChanges();
518+
combo.onBlur();
519+
tick();
520+
fixture.detectChanges();
521+
expect(input.nativeElement.value.length).toEqual(0);
522+
expect(combo.selection.length).toEqual(0);
523+
}));
524+
525+
it('should move the focus to the AddItem button with ArrowDown when allowCustomItems is true', fakeAsync(() => {
526+
fixture.componentInstance.allowCustomValues = true;
527+
fixture.detectChanges();
528+
UIInteractions.setInputElementValue(input.nativeElement, 'MassachusettsL');
529+
fixture.detectChanges();
530+
combo.open();
531+
fixture.detectChanges();
532+
const addItemButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_ADDBUTTON}`));
533+
expect(addItemButton).toBeDefined();
534+
535+
input.nativeElement.focus();
536+
fixture.detectChanges();
537+
538+
combo.onArrowDown(new Event('keydown'));
539+
fixture.detectChanges();
540+
expect(document.activeElement).toEqual(addItemButton.nativeElement);
541+
}));
507542
});
508543
});
509544

510545
@Component({
511546
template: `
512547
<igx-simple-combo #combo [placeholder]="'Location'" [data]='items' [displayDensity]="density"
513-
[valueKey]="'field'" [groupKey]="'region'" [width]="'400px'" (selectionChanging)="selectionChanging($event)">
548+
[valueKey]="'field'" [groupKey]="'region'" [width]="'400px'" (selectionChanging)="selectionChanging($event)"
549+
[allowCustomValues]="allowCustomValues">
514550
<ng-template igxComboItem let-display let-key="valueKey">
515551
<div class="state-card--simple">
516552
<span class="small-red-circle"></span>
@@ -531,6 +567,7 @@ class IgxSimpleComboSampleComponent {
531567
@ViewChild('combo', { read: IgxSimpleComboComponent, static: true })
532568
public combo: IgxSimpleComboComponent;
533569
public density: DisplayDensity = DisplayDensity.cosy;
570+
public allowCustomValues = false;
534571

535572
public items = [];
536573
public initData = [];

0 commit comments

Comments
 (0)