Skip to content

Commit d3810a9

Browse files
feat(single-combo): initial implementation
1 parent e728fc1 commit d3810a9

File tree

14 files changed

+517
-112
lines changed

14 files changed

+517
-112
lines changed

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
ChangeDetectorRef, Component, ElementRef, Inject, QueryList, OnDestroy, AfterViewInit, ContentChildren, Optional
2+
ChangeDetectorRef, Component, ElementRef, Inject, QueryList, OnDestroy, AfterViewInit, ContentChildren, Optional, Input
33
} from '@angular/core';
44
import { IgxComboBase, IGX_COMBO_COMPONENT } from './combo.common';
55
import { IDropDownBase, IGX_DROPDOWN_BASE } from '../drop-down/drop-down.common';
@@ -20,6 +20,10 @@ import { PlatformUtil } from '../core/utils';
2020
providers: [{ provide: IGX_DROPDOWN_BASE, useExisting: IgxComboDropDownComponent }]
2121
})
2222
export class IgxComboDropDownComponent extends IgxDropDownComponent implements IDropDownBase, OnDestroy, AfterViewInit {
23+
/** @hidden @internal */
24+
@Input()
25+
public singleMode = false;
26+
2327
/**
2428
* @hidden
2529
* @internal
@@ -192,9 +196,13 @@ export class IgxComboDropDownComponent extends IgxDropDownComponent implements I
192196
private handleEnter() {
193197
if (this.isAddItemFocused()) {
194198
this.combo.addItemToCollection();
195-
} else {
196-
this.close();
199+
return;
200+
}
201+
if (this.singleMode && this.focusedItem) {
202+
this.combo.select(this.focusedItem.itemID);
197203
}
204+
205+
this.close();
198206
}
199207

200208
private handleSpace() {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<ng-container *ngIf="!isHeader">
1+
<ng-container *ngIf="!isHeader && !singleMode">
22
<igx-checkbox [checked]="selected" [disableRipple]="true" [disableTransitions]="disableTransitions" [tabindex]="-1" (click)="disableCheck($event)" class="igx-combo__checkbox"></igx-checkbox>
33
</ng-container>
44
<span class="igx-drop-down__inner"><ng-content></ng-content></span>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export class IgxComboItemComponent extends IgxDropDownItemComponent implements D
2727
@HostBinding('style.height.px')
2828
public itemHeight: string | number = '';
2929

30+
@Input()
31+
public singleMode: boolean;
32+
3033
/**
3134
* @hidden
3235
*/

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ export class IgxComboAPIService {
4545
return;
4646
}
4747
if (!selected) {
48-
this.combo.selectItems([itemID], false, event);
48+
this.combo.select([itemID], false, event);
4949
} else {
50-
this.combo.deselectItems([itemID], event);
50+
this.combo.deselect([itemID], event);
5151
}
5252
}
5353

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface IgxComboBase {
2727
isAddButtonVisible(): boolean;
2828
handleInputChange(event?: string): void;
2929
isItemSelected(itemID: any): boolean;
30-
selectItems(itemIDs: any[], clearSelection?: boolean, event?: Event): void;
31-
deselectItems(itemIDs: any[], event?: Event): void;
30+
select(item: any);
31+
select(itemIDs: any[], clearSelection?: boolean, event?: Event): void;
32+
deselect(item: any);
33+
deselect(itemIDs: any[], event?: Event): void;
3234
}

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

Lines changed: 42 additions & 42 deletions
Large diffs are not rendered by default.

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

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { IgxIconModule, IgxIconService } from '../icon/public_api';
2424
import { IgxRippleModule } from '../directives/ripple/ripple.directive';
2525
import { IgxToggleModule } from '../directives/toggle/toggle.directive';
2626
import { IgxButtonModule } from '../directives/button/button.directive';
27-
import { IgxDropDownModule } from '../drop-down/public_api';
27+
import { IgxDropDownModule, ISelectionEventArgs } from '../drop-down/public_api';
2828
import { IgxInputGroupModule, IgxInputGroupComponent } from '../input-group/input-group.component';
2929
import { IgxComboItemComponent } from './combo-item.component';
3030
import { IgxComboDropDownComponent } from './combo-dropdown.component';
@@ -143,7 +143,7 @@ let NEXT_ID = 0;
143143
* @igxGroup Grids & Lists
144144
*
145145
* @remarks
146-
* It provides the ability to filter items as well as perform single or multiple seleciton with the provided data.
146+
* It provides the ability to filter items as well as perform single or multiple selection with the provided data.
147147
* Additionally, it exposes keyboard navigation and custom styling capabilities.
148148
* @example
149149
* ```html
@@ -402,7 +402,7 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
402402
* ```
403403
*/
404404
@Output()
405-
public selectionChanging = new EventEmitter<IComboSelectionChangingEventArgs>();
405+
public selectionChanging = new EventEmitter<IComboSelectionChangingEventArgs | ISelectionEventArgs>();
406406

407407
/**
408408
* Emitted before the dropdown is opened
@@ -910,33 +910,33 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
910910
protected _groupKey = '';
911911
protected _displayKey: string;
912912
protected _prevInputValue = '';
913+
protected _value = '';
914+
protected _valid = IgxComboState.INITIAL;
915+
protected destroy$ = new Subject<any>();
916+
protected _onChangeCallback: (_: any) => void = noop;
917+
protected _data = [];
913918

914919
private _dataType = '';
915920
private _searchValue = '';
916921
private _type = null;
917922
private ngControl: NgControl = null;
918-
private destroy$ = new Subject<any>();
919-
private _data = [];
920923
private _filteredData = [];
921924
private _itemHeight = null;
922925
private _itemsMaxHeight = null;
923926
private _remoteSelection = {};
924-
private _onChangeCallback: (_: any) => void = noop;
925927
private _onTouchedCallback: () => void = noop;
926928
private _overlaySettings: OverlaySettings;
927-
private _value = '';
928-
private _valid = IgxComboState.INITIAL;
929929
private _groupSortingDirection: SortingDirection = SortingDirection.Asc;
930930

931931
constructor(
932932
protected elementRef: ElementRef,
933933
protected cdr: ChangeDetectorRef,
934934
protected selection: IgxSelectionAPIService,
935935
protected comboAPI: IgxComboAPIService,
936-
private _iconService: IgxIconService,
936+
protected _iconService: IgxIconService,
937937
@Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions,
938-
@Optional() @Inject(IGX_INPUT_GROUP_TYPE) private _inputGroupType: IgxInputGroupType,
939-
@Optional() private _injector: Injector) {
938+
@Optional() @Inject(IGX_INPUT_GROUP_TYPE) protected _inputGroupType: IgxInputGroupType,
939+
@Optional() protected _injector: Injector) {
940940
super(_displayDensityOptions);
941941
this.comboAPI.register(this);
942942
}
@@ -1021,6 +1021,10 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
10211021
return this._value;
10221022
}
10231023

1024+
public set value(val: string) {
1025+
this._value = val;
1026+
}
1027+
10241028
/**
10251029
* @hidden @internal
10261030
*/
@@ -1040,6 +1044,7 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
10401044
* @hidden @internal
10411045
*/
10421046
public handleKeyUp(event: KeyboardEvent): void {
1047+
// TODO: use PlatformUtil for keyboard navigation
10431048
if (event.key === 'ArrowDown' || event.key === 'Down') {
10441049
this.dropdown.focusedItem = this.dropdown.items[0];
10451050
this.dropdownContainer.nativeElement.focus();
@@ -1062,10 +1067,10 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
10621067
/**
10631068
* @hidden @internal
10641069
*/
1065-
public handleInputChange(event?: string) {
1070+
public handleInputChange(event?: any) {
10661071
if (event !== undefined) {
10671072
const args: IComboSearchInputEventArgs = {
1068-
searchText: event,
1073+
searchText: typeof event === 'string' ? event : event.target.value,
10691074
owner: this,
10701075
cancel: false
10711076
};
@@ -1160,10 +1165,10 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
11601165
this.data.push(args.addedItem);
11611166
// trigger re-render
11621167
this.data = cloneArray(this.data);
1163-
this.selectItems(this.valueKey !== null && this.valueKey !== undefined ?
1168+
this.select(this.valueKey !== null && this.valueKey !== undefined ?
11641169
[args.addedItem[this.valueKey]] : [args.addedItem], false);
11651170
this.customValueFlag = false;
1166-
this.searchInput.nativeElement.focus();
1171+
this.searchInput?.nativeElement.focus();
11671172
this.dropdown.focusedItem = null;
11681173
this.virtDir.scrollTo(0);
11691174
}
@@ -1389,10 +1394,10 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
13891394
* @param newItems new items to be selected
13901395
* @param clearCurrentSelection if true clear previous selected items
13911396
* ```typescript
1392-
* this.combo.selectItems(["New York", "New Jersey"]);
1397+
* this.combo.select(["New York", "New Jersey"]);
13931398
* ```
13941399
*/
1395-
public selectItems(newItems: Array<any>, clearCurrentSelection?: boolean, event?: Event) {
1400+
public select(newItems: Array<any>, clearCurrentSelection?: boolean, event?: Event) {
13961401
if (newItems) {
13971402
const newSelection = this.selection.add_items(this.id, newItems, clearCurrentSelection);
13981403
this.setSelection(newSelection, event);
@@ -1404,10 +1409,10 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
14041409
*
14051410
* @param items items to deselected
14061411
* ```typescript
1407-
* this.combo.deselectItems(["New York", "New Jersey"]);
1412+
* this.combo.deselect(["New York", "New Jersey"]);
14081413
* ```
14091414
*/
1410-
public deselectItems(items: Array<any>, event?: Event) {
1415+
public deselect(items: Array<any>, event?: Event) {
14111416
if (items) {
14121417
const newSelection = this.selection.delete_items(this.id, items);
14131418
this.setSelection(newSelection, event);
@@ -1468,9 +1473,9 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
14681473
return;
14691474
}
14701475
if (select) {
1471-
this.selectItems([itemID], false, event);
1476+
this.select([itemID], false, event);
14721477
} else {
1473-
this.deselectItems([itemID], event);
1478+
this.deselect([itemID], event);
14741479
}
14751480
}
14761481
/**
@@ -1513,7 +1518,9 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
15131518
return;
15141519
}
15151520
this.searchValue = '';
1516-
this.comboInput.nativeElement.focus();
1521+
if (!e.event) {
1522+
this.comboInput?.nativeElement.focus();
1523+
}
15171524
}
15181525

15191526
/**
@@ -1581,6 +1588,40 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
15811588
}
15821589
}
15831590

1591+
/**
1592+
* Constructs the combo display value
1593+
* If remote, caches the key displayText
1594+
* If not, just combine the object.displayKeys
1595+
*/
1596+
protected createDisplayText(newSelection: any[], oldSelection: any[]) {
1597+
let value = '';
1598+
if (this.isRemote) {
1599+
if (newSelection.length) {
1600+
const removedItems = oldSelection.filter(e => newSelection.indexOf(e) < 0);
1601+
const addedItems = newSelection.filter(e => oldSelection.indexOf(e) < 0);
1602+
this.registerRemoteEntries(addedItems);
1603+
this.registerRemoteEntries(removedItems, false);
1604+
value = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e]).join(', ');
1605+
} else {
1606+
// If new selection is empty, clear all items
1607+
this.registerRemoteEntries(oldSelection, false);
1608+
}
1609+
} else {
1610+
value = this.concatDisplayText(newSelection);
1611+
}
1612+
return value;
1613+
}
1614+
1615+
protected checkMatch(): void {
1616+
const itemMatch = this.filteredData.some(this.findMatch);
1617+
this.customValueFlag = this.allowCustomValues && !itemMatch;
1618+
}
1619+
1620+
protected findMatch = (element: any): boolean => {
1621+
const value = this.displayKey ? element[this.displayKey] : element;
1622+
return value.toString().toLowerCase() === this.searchValue.trim().toLowerCase();
1623+
};
1624+
15841625
/** Contains key-value pairs of the selected valueKeys and their resp. displayKeys */
15851626
private registerRemoteEntries(ids: any[], add = true) {
15861627
if (add) {
@@ -1605,16 +1646,6 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
16051646
}));
16061647
}
16071648

1608-
private checkMatch(): void {
1609-
const displayKey = this.displayKey;
1610-
const matchFn = (e) => {
1611-
const value = displayKey ? e[displayKey] : e;
1612-
return value.toString().toLowerCase() === this.searchValue.trim().toLowerCase();
1613-
};
1614-
const itemMatch = this.filteredData.some(matchFn);
1615-
this.customValueFlag = this.allowCustomValues && !itemMatch;
1616-
}
1617-
16181649
/** Returns a string that should be populated in the combo's text box */
16191650
private concatDisplayText(selection: any[]): string {
16201651
const value = this.displayKey !== null && this.displayKey !== undefined ?
@@ -1623,30 +1654,6 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
16231654
return value;
16241655
}
16251656

1626-
/**
1627-
* Constructs the combo display value
1628-
* If remote, caches the key displayText
1629-
* If not, just combine the object.displayKeys
1630-
*/
1631-
private createDisplayText(newSelection: any[], oldSelection: any[]) {
1632-
let value = '';
1633-
if (this.isRemote) {
1634-
if (newSelection.length) {
1635-
const removedItems = oldSelection.filter(e => newSelection.indexOf(e) < 0);
1636-
const addedItems = newSelection.filter(e => oldSelection.indexOf(e) < 0);
1637-
this.registerRemoteEntries(addedItems);
1638-
this.registerRemoteEntries(removedItems, false);
1639-
value = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e]).join(', ');
1640-
} else {
1641-
// If new selection is empty, clear all items
1642-
this.registerRemoteEntries(oldSelection, false);
1643-
}
1644-
} else {
1645-
value = this.concatDisplayText(newSelection);
1646-
}
1647-
return value;
1648-
}
1649-
16501657
/** if there is a valueKey - map the keys to data items, else - just return the keys */
16511658
private convertKeysToItems(keys: any[]) {
16521659
if (this.comboAPI.valueKey === null) {
@@ -1672,6 +1679,8 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
16721679
IgxComboToggleIconDirective,
16731680
IgxComboClearIconDirective],
16741681
exports: [IgxComboComponent, IgxComboItemComponent, IgxComboDropDownComponent, IgxComboAddItemComponent,
1682+
IgxComboGroupingPipe, // TODO: create common ng module
1683+
IgxComboFilteringPipe,
16751684
IgxComboItemDirective,
16761685
IgxComboEmptyDirective,
16771686
IgxComboHeaderItemDirective,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class IgxComboFilteringPipe implements PipeTransform {
2828
e[displayKey].toString().toLowerCase().includes(searchTerm));
2929
} else {
3030
return collection.filter(e => filteringOptions.caseSensitive ? e.includes(searchTerm) :
31-
e.toLowerCase().includes(searchTerm));
31+
e.toString().toLowerCase().includes(searchTerm));
3232
}
3333
}
3434
}

projects/igniteui-angular/src/lib/grids/grid/grid.nested.props.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ describe('Edit cell with data of type Array #grid', () => {
547547
expect(cell.editMode).toBeTruthy();
548548
expect(combo.selectedItems().length).toEqual(3);
549549

550-
combo.deselectItems([cell.editValue[0], cell.editValue[1]]);
550+
combo.deselect([cell.editValue[0], cell.editValue[1]]);
551551
fixture.detectChanges();
552552
await fixture.whenStable();
553553

@@ -609,7 +609,7 @@ describe('Edit cell with data of type Array #grid', () => {
609609
expect(cell.editMode).toBeTruthy();
610610
expect(combo.selectedItems().length).toEqual(3);
611611

612-
combo.deselectItems([cell.editValue[0], cell.editValue[1]]);
612+
combo.deselect([cell.editValue[0], cell.editValue[1]]);
613613
fixture.detectChanges();
614614
await fixture.whenStable();
615615

@@ -677,7 +677,7 @@ describe('Edit cell with data of type Array #grid', () => {
677677
expect(cell.editMode).toBeTruthy();
678678
expect(combo.selectedItems().length).toEqual(3);
679679

680-
combo.deselectItems([cell.editValue[0], cell.editValue[1]]);
680+
combo.deselect([cell.editValue[0], cell.editValue[1]]);
681681
fixture.detectChanges();
682682
await fixture.whenStable();
683683

@@ -740,7 +740,7 @@ describe('Edit cell with data of type Array #grid', () => {
740740
expect(cell.editMode).toBeTruthy();
741741
expect(combo.selectedItems().length).toEqual(3);
742742

743-
combo.deselectItems([cell.editValue[0], cell.editValue[1]]);
743+
combo.deselect([cell.editValue[0], cell.editValue[1]]);
744744
fixture.detectChanges();
745745
await fixture.whenStable();
746746

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './simple-combo.component';

0 commit comments

Comments
 (0)