Skip to content

Commit de22714

Browse files
committed
fix(menu): scroll active item into view when using arrow keys
Fixes #6
1 parent dd1f99c commit de22714

File tree

2 files changed

+41
-14
lines changed

2 files changed

+41
-14
lines changed

src/text-input-autocomplete-menu.component.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import { Component, HostListener } from '@angular/core';
1+
import { Component, ElementRef, HostListener, ViewChild } from '@angular/core';
22
import { Subject } from 'rxjs';
33

44
@Component({
55
template: `
66
<ul
77
*ngIf="choices?.length > 0"
8+
#dropdownMenu
89
class="dropdown-menu"
910
[style.top.px]="position?.top"
1011
[style.left.px]="position?.left">
11-
<li
12+
<li
1213
*ngFor="let choice of choices"
1314
[class.active]="activeChoice === choice">
1415
<a
@@ -21,15 +22,16 @@ import { Subject } from 'rxjs';
2122
`,
2223
styles: [
2324
`
24-
.dropdown-menu {
25-
display: block;
26-
max-height: 200px;
27-
overflow-y: auto;
28-
}
29-
`
25+
.dropdown-menu {
26+
display: block;
27+
max-height: 200px;
28+
overflow-y: auto;
29+
}
30+
`
3031
]
3132
})
3233
export class TextInputAutocompleteMenuComponent {
34+
@ViewChild('dropdownMenu') dropdownMenuElement: ElementRef<HTMLUListElement>;
3335
position: { top: number; left: number };
3436
selectChoice = new Subject();
3537
activeChoice: any;
@@ -54,7 +56,7 @@ export class TextInputAutocompleteMenuComponent {
5456
event.preventDefault();
5557
const index = this.choices.indexOf(this.activeChoice);
5658
if (this.choices[index + 1]) {
57-
this.activeChoice = this._choices[index + 1];
59+
this.scrollToChoice(index + 1);
5860
}
5961
}
6062

@@ -63,7 +65,7 @@ export class TextInputAutocompleteMenuComponent {
6365
event.preventDefault();
6466
const index = this.choices.indexOf(this.activeChoice);
6567
if (this.choices[index - 1]) {
66-
this.activeChoice = this._choices[index - 1];
68+
this.scrollToChoice(index - 1);
6769
}
6870
}
6971

@@ -74,4 +76,18 @@ export class TextInputAutocompleteMenuComponent {
7476
this.selectChoice.next(this.activeChoice);
7577
}
7678
}
79+
80+
private scrollToChoice(index: number) {
81+
this.activeChoice = this._choices[index];
82+
if (this.dropdownMenuElement) {
83+
const ulPosition = this.dropdownMenuElement.nativeElement.getBoundingClientRect();
84+
const li = this.dropdownMenuElement.nativeElement.children[index];
85+
const liPosition = li.getBoundingClientRect();
86+
if (liPosition.top < ulPosition.top) {
87+
li.scrollIntoView();
88+
} else if (liPosition.bottom > ulPosition.bottom) {
89+
li.scrollIntoView(false);
90+
}
91+
}
92+
}
7793
}

test/text-input-autocomplete.directive.spec.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,15 @@ describe('text-input-autocomplete directive', () => {
109109
return fixture.debugElement.query(By.directive(menuComponent));
110110
}
111111

112-
function arrowDown() {
113-
getMenu().componentInstance.onArrowDown({
112+
function arrowDown(menuComponent = TextInputAutocompleteMenuComponent) {
113+
getMenu(menuComponent).componentInstance.onArrowDown({
114114
preventDefault: sinon.spy()
115115
});
116116
fixture.detectChanges();
117117
}
118118

119-
function arrowUp() {
120-
getMenu().componentInstance.onArrowUp({
119+
function arrowUp(menuComponent = TextInputAutocompleteMenuComponent) {
120+
getMenu(menuComponent).componentInstance.onArrowUp({
121121
preventDefault: sinon.spy()
122122
});
123123
fixture.detectChanges();
@@ -363,4 +363,15 @@ describe('text-input-autocomplete directive', () => {
363363
expect(getMenu().componentInstance.choiceLoading).to.be.false;
364364
})
365365
);
366+
367+
it(
368+
'should not throw when using a custom template and trying to scroll',
369+
fakeAsync(() => {
370+
component.menuComponent = CustomMenuComponent;
371+
fixture.detectChanges();
372+
typeInTextarea('text @def');
373+
flush();
374+
expect(() => arrowDown(CustomMenuComponent)).not.to.throw();
375+
})
376+
);
366377
});

0 commit comments

Comments
 (0)