Skip to content

Commit 847d375

Browse files
committed
fix(typeahead): blur event handler should not prevent item selection
fixes #403, fixes #418, fixes #356
1 parent b1a95d1 commit 847d375

File tree

3 files changed

+44
-15
lines changed

3 files changed

+44
-15
lines changed

components/typeahead/typeahead-container.component.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import {Typeahead} from './typeahead.directive';
55
import {TypeaheadOptions} from './typeahead-options.class';
66
import {positionService} from '../position';
77
import {Ng2BootstrapConfig, Ng2BootstrapTheme} from '../ng2-bootstrap-config';
8-
98
const TEMPLATE:any = {
109
[Ng2BootstrapTheme.BS4]: `
1110
<div class="dropdown-menu"
11+
style="display: block"
1212
[ngStyle]="{top: top, left: left, display: display}"
13-
style="display: block">
13+
(mouseleave)="focusLost()">
1414
<a href="#"
1515
*ngFor="#match of matches"
1616
class="dropdown-item"
@@ -22,8 +22,9 @@ const TEMPLATE:any = {
2222
`,
2323
[Ng2BootstrapTheme.BS3]: `
2424
<ul class="dropdown-menu"
25+
style="display: block"
2526
[ngStyle]="{top: top, left: left, display: display}"
26-
style="display: block">
27+
(mouseleave)="focusLost()">
2728
<li *ngFor="#match of matches"
2829
[class.active]="isActive(match)"
2930
(mouseenter)="selectActive(match)">
@@ -32,7 +33,6 @@ const TEMPLATE:any = {
3233
</ul>
3334
`
3435
};
35-
3636
@Component({
3737
selector: 'typeahead-container',
3838
directives: [CORE_DIRECTIVES],
@@ -43,9 +43,10 @@ export class TypeaheadContainer {
4343
public parent:Typeahead;
4444
public query:any;
4545
public element:ElementRef;
46+
public isFocused:boolean = false;
47+
private _active:any;
4648
private _matches:Array<any> = [];
4749
private _field:string;
48-
private _active:any;
4950
private top:string;
5051
private left:string;
5152
private display:string;
@@ -62,7 +63,6 @@ export class TypeaheadContainer {
6263

6364
public set matches(value:Array<string>) {
6465
this._matches = value;
65-
6666
if (this._matches.length > 0) {
6767
this._active = this._matches[0];
6868
}
@@ -103,6 +103,7 @@ export class TypeaheadContainer {
103103
}
104104

105105
protected selectActive(value:any):void {
106+
this.isFocused = true;
106107
this._active = value;
107108
}
108109

@@ -115,7 +116,6 @@ export class TypeaheadContainer {
115116
: itemStr).toLowerCase();
116117
let startIdx:number;
117118
let tokenLen:number;
118-
119119
// Replaces the capture string with the same string inside of a "strong" tag
120120
if (typeof query === 'object') {
121121
let queryLen:number = query.length;
@@ -136,7 +136,6 @@ export class TypeaheadContainer {
136136
itemStr = itemStr.substring(0, startIdx) + '<strong>' + itemStr.substring(startIdx, startIdx + tokenLen) + '</strong>' + itemStr.substring(startIdx + tokenLen);
137137
}
138138
}
139-
140139
return itemStr;
141140
}
142141

@@ -149,11 +148,14 @@ export class TypeaheadContainer {
149148
e.stopPropagation();
150149
e.preventDefault();
151150
}
152-
153151
this.parent.changeModel(value);
154152
this.parent.typeaheadOnSelect.emit({
155153
item: value
156154
});
157155
return false;
158156
}
157+
158+
private focusLost():void {
159+
this.isFocused = false;
160+
}
159161
}

components/typeahead/typeahead-options.class.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import {Typeahead}from './typeahead.directive';
2+
13
export class TypeaheadOptions {
24
public placement:string;
35
public animation:boolean;
6+
public typeaheadRef:Typeahead;
47

58
public constructor(options:TypeaheadOptions) {
69
Object.assign(this, options);

components/typeahead/typeahead.directive.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
Directive, Input, Output, HostListener, EventEmitter, OnInit, ElementRef,
3-
Renderer, DynamicComponentLoader, ComponentRef, Provider, Injector
3+
Renderer, DynamicComponentLoader, ComponentRef, Injector, provide
44
} from 'angular2/core';
55
import {NgModel} from 'angular2/common';
66
import {TypeaheadUtils} from './typeahead-utils';
@@ -41,12 +41,14 @@ export class Typeahead implements OnInit {
4141
// @Input() private typeaheadFocusOnSelect:boolean;
4242

4343
public container:TypeaheadContainer;
44+
public isTypeaheadOptionsListActive:boolean = false;
4445

4546
private debouncer:Function;
4647
private _matches:Array<any> = [];
4748
private placement:string = 'bottom-left';
4849
private popup:Promise<ComponentRef>;
4950

51+
5052
private cd:NgModel;
5153
private element:ElementRef;
5254
private renderer:Renderer;
@@ -102,15 +104,36 @@ export class Typeahead implements OnInit {
102104

103105
@HostListener('blur', ['$event.target'])
104106
protected onBlur():void {
105-
// Allow typeahead container click event to be triggered requires a timeout
106-
setTimeout(this.hide.bind(this), 10);
107+
console.log('blur')
108+
if (this.container && !this.container.isFocused) {
109+
console.log('blur hide')
110+
this.hide();
111+
}
107112
}
108113

109114
@HostListener('keydown', ['$event'])
110115
protected onKeydown(e:KeyboardEvent):void {
111-
// When typeahead container is visible, prevent submitting the form
112-
if (this.container && e.keyCode === 13) {
116+
// no container - no problems
117+
if (!this.container) {
118+
return;
119+
}
120+
121+
// if items is visible - prevent form submition
122+
if (e.keyCode === 13) {
113123
e.preventDefault();
124+
return;
125+
}
126+
127+
// if shift + tab, close items list
128+
if (e.shiftKey && e.keyCode === 9) {
129+
this.hide();
130+
return;
131+
}
132+
133+
// if tab select current item
134+
if (!e.shiftKey && e.keyCode === 9) {
135+
this.container.selectActiveMatch();
136+
return;
114137
}
115138
}
116139

@@ -166,12 +189,13 @@ export class Typeahead implements OnInit {
166189

167190
public show(matches:Array<any>):void {
168191
let options = new TypeaheadOptions({
192+
typeaheadRef: this,
169193
placement: this.placement,
170194
animation: false
171195
});
172196

173197
let binding = Injector.resolve([
174-
new Provider(TypeaheadOptions, {useValue: options})
198+
provide(TypeaheadOptions, {useValue: options})
175199
]);
176200

177201
this.popup = this.loader

0 commit comments

Comments
 (0)