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

IgniteUI / igniteui-angular / 18007030997

25 Sep 2025 12:11PM UTC coverage: 91.587% (+0.005%) from 91.582%
18007030997

Pull #16246

github

web-flow
Merge 410ba571d into 894a1d18a
Pull Request #16246: Update Combo and Simple Combo Keyboard Navigation & Add Escape Key behavior

13841 of 16226 branches covered (85.3%)

25 of 26 new or added lines in 5 files covered. (96.15%)

19 existing lines in 4 files now uncovered.

27794 of 30347 relevant lines covered (91.59%)

34867.7 hits per line

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

85.71
/projects/igniteui-angular/src/lib/combo/combo-dropdown.component.ts
1
import {
2
    ChangeDetectorRef, Component, ElementRef, Inject, QueryList, OnDestroy, AfterViewInit, ContentChildren, Input, booleanAttribute, DOCUMENT
3
} from '@angular/core';
4
import { IgxComboBase, IGX_COMBO_COMPONENT } from './combo.common';
5
import { IDropDownBase, IGX_DROPDOWN_BASE } from '../drop-down/drop-down.common';
6
import { IgxDropDownComponent } from '../drop-down/drop-down.component';
7
import { DropDownActionKey } from '../drop-down/drop-down.common';
8
import { IgxComboAddItemComponent } from './combo-add-item.component';
9
import { IgxComboAPIService } from './combo.api';
10
import { IgxDropDownItemBaseDirective } from '../drop-down/drop-down-item.base';
11
import { IgxSelectionAPIService } from '../core/selection';
12
import { IgxComboItemComponent } from './combo-item.component';
13
import { IgxToggleDirective } from '../directives/toggle/toggle.directive';
14

15
/** @hidden */
16
@Component({
17
    selector: 'igx-combo-drop-down',
18
    templateUrl: '../drop-down/drop-down.component.html',
19
    providers: [{ provide: IGX_DROPDOWN_BASE, useExisting: IgxComboDropDownComponent }],
20
    imports: [IgxToggleDirective]
21
})
22
export class IgxComboDropDownComponent extends IgxDropDownComponent implements IDropDownBase, OnDestroy, AfterViewInit {
3✔
23
    /** @hidden @internal */
24
    @Input({ transform: booleanAttribute })
25
    public singleMode = false;
391✔
26

27
    /**
28
     * @hidden
29
     * @internal
30
     */
31
    @ContentChildren(IgxComboItemComponent, { descendants: true })
32
    public override children: QueryList<IgxDropDownItemBaseDirective> = null;
391✔
33

34
    /** @hidden @internal */
35
    public override get scrollContainer(): HTMLElement {
36
        // TODO: Update, use public API if possible:
37
        return this.virtDir.dc.location.nativeElement;
1✔
38
    }
39

40
    protected get isScrolledToLast(): boolean {
41
        const scrollTop = this.virtDir.scrollPosition;
×
42
        const scrollHeight = this.virtDir.getScroll().scrollHeight;
×
43
        return Math.floor(scrollTop + this.virtDir.igxForContainerSize) === scrollHeight;
×
44
    }
45

46
    protected get lastVisibleIndex(): number {
47
        return this.combo.totalItemCount ?
×
48
            Math.floor(this.combo.itemsMaxHeight / this.combo.itemHeight) :
49
            this.items.length - 1;
50
    }
51

52
    protected get sortedChildren(): IgxDropDownItemBaseDirective[] {
53
        if (this.children !== undefined) {
124✔
54
            return this.children.toArray()
124✔
55
                .sort((a: IgxDropDownItemBaseDirective, b: IgxDropDownItemBaseDirective) => a.index - b.index);
711✔
56
        }
57
        return null;
×
58
    }
59

60
    /**
61
     * Get all non-header items
62
     *
63
     * ```typescript
64
     * let myDropDownItems = this.dropdown.items;
65
     * ```
66
     */
67
    public override get items(): IgxComboItemComponent[] {
68
        const items: IgxComboItemComponent[] = [];
124✔
69
        if (this.children !== undefined) {
124✔
70
            const sortedChildren = this.sortedChildren as IgxComboItemComponent[];
124✔
71
            for (const child of sortedChildren) {
124✔
72
                if (!child.isHeader) {
828✔
73
                    items.push(child);
625✔
74
                }
75
            }
76
        }
77

78
        return items;
124✔
79
    }
80

81
    constructor(
82
        elementRef: ElementRef,
83
        cdr: ChangeDetectorRef,
84
        @Inject(DOCUMENT) document: any,
85
        selection: IgxSelectionAPIService,
86
        @Inject(IGX_COMBO_COMPONENT) public combo: IgxComboBase,
391✔
87
        protected comboAPI: IgxComboAPIService) {
391✔
88
        super(elementRef, cdr, document, selection);
391✔
89
    }
90

91
    /**
92
     * @hidden @internal
93
     */
94
    public onFocus() {
95
        this.focusedItem = this._focusedItem || this.items[0];
30✔
96
        this.combo.setActiveDescendant();
30✔
97
    }
98

99
    /**
100
     * @hidden @internal
101
     */
102
    public onBlur(_evt?) {
103
        this.focusedItem = null;
59✔
104
        this.combo.setActiveDescendant();
59✔
105
    }
106

107
    /**
108
     * @hidden @internal
109
     */
110
    public override onToggleOpened() {
111
        this.opened.emit();
78✔
112
    }
113

114
    /**
115
     * @hidden
116
     */
117
    public override navigateFirst() {
118
        this.navigateItem(this.virtDir.igxForOf.findIndex(e => !e?.isHeader));
54✔
119
        this.combo.setActiveDescendant();
29✔
120
    }
121

122
    /**
123
     * @hidden
124
     */
125
    public override navigatePrev() {
126
        if (this._focusedItem && this._focusedItem.index === 0 && this.virtDir.state.startIndex === 0) {
8✔
127
            this.combo.focusSearchInput(false);
2✔
128
            this.focusedItem = null;
2✔
129
        } else {
130
            super.navigatePrev();
6✔
131
        }
132
        this.combo.setActiveDescendant();
8✔
133
    }
134

135

136
    /**
137
     * @hidden
138
     */
139
    public override navigateNext() {
140
        const lastIndex = this.combo.totalItemCount ? this.combo.totalItemCount - 1 : this.virtDir.igxForOf.length - 1;
24!
141
        if (this._focusedItem && this._focusedItem.index === lastIndex) {
24✔
142
            this.focusAddItemButton();
1✔
143
        } else {
144
            super.navigateNext();
23✔
145
        }
146
        this.combo.setActiveDescendant();
24✔
147
    }
148

149
    /**
150
     * @hidden @internal
151
     */
152
    public override selectItem(item: IgxDropDownItemBaseDirective) {
153
        if (item === null || item === undefined) {
7!
154
            return;
×
155
        }
156
        this.comboAPI.set_selected_item(item.itemID);
7✔
157
        this._focusedItem = item;
7✔
158
        this.combo.setActiveDescendant();
7✔
159
    }
160

161
    /**
162
     * @hidden @internal
163
     */
164
    public override updateScrollPosition() {
165
        this.virtDir.getScroll().scrollTop = this._scrollPosition;
173✔
166
    }
167

168
    /**
169
     * @hidden @internal
170
     */
171
    public override onItemActionKey(key: DropDownActionKey, event?: KeyboardEvent) {
172
        switch (key) {
17✔
173
            case DropDownActionKey.ENTER:
174
                this.handleEnter();
1✔
175
                break;
1✔
176
            case DropDownActionKey.SPACE:
177
                this.handleSpace();
9✔
178
                break;
9✔
179
            case DropDownActionKey.ESCAPE:
180
                this.close();
2✔
181
                break;
2✔
182
            case DropDownActionKey.TAB:
183
                this.close(event);
5✔
184
        }
185
    }
186

187
    public override ngAfterViewInit() {
188
        this.virtDir.getScroll().addEventListener('scroll', this.scrollHandler);
391✔
189
    }
190

191
    /**
192
     * @hidden @internal
193
     */
194
    public override ngOnDestroy(): void {
195
        this.virtDir.getScroll().removeEventListener('scroll', this.scrollHandler);
391✔
196
        super.ngOnDestroy();
391✔
197
    }
198

199
    protected override scrollToHiddenItem(_newItem: any): void { }
200

201
    protected scrollHandler = () => {
391✔
202
        this.comboAPI.disableTransitions = true;
20✔
203
    };
204

205
    private handleEnter() {
206
        if (this.isAddItemFocused()) {
1✔
207
            this.combo.addItemToCollection();
1✔
208
            return;
1✔
209
        }
UNCOV
210
        if (this.singleMode && this.focusedItem) {
×
UNCOV
211
            this.combo.select(this.focusedItem.itemID);
×
212
        }
213

UNCOV
214
        this.close();
×
215
    }
216

217
    private handleSpace() {
218
        if (this.isAddItemFocused()) {
9✔
219
            return;
2✔
220
        } else {
221
            this.selectItem(this.focusedItem);
7✔
222
        }
223
    }
224

225
    private isAddItemFocused(): boolean {
226
        return this.focusedItem instanceof IgxComboAddItemComponent;
10✔
227
    }
228

229
    private focusAddItemButton() {
230
        if (this.combo.isAddButtonVisible()) {
1!
UNCOV
231
            this.focusedItem = this.items[this.items.length - 1];
×
232
        }
233
    }
234
}
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