Skip to content

Commit 9c785d2

Browse files
feat: allow opening the menu with a keyboard shortcut
1 parent 3610bb8 commit 9c785d2

File tree

3 files changed

+44
-2
lines changed

3 files changed

+44
-2
lines changed

demo/demo.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const names = [
3434
rows="5"
3535
[(ngModel)]="formControlValue"
3636
mwlTextInputAutocomplete
37+
[keyboardShortcut]="shortcut"
3738
[findChoices]="findChoices"
3839
[getChoiceLabel]="getChoiceLabel">
3940
</textarea>
@@ -52,4 +53,8 @@ export class DemoComponent {
5253
getChoiceLabel(choice: string) {
5354
return `@${choice} `;
5455
}
56+
57+
shortcut(event: KeyboardEvent): boolean {
58+
return (event.keyCode === 32 || event.code === '32') && event.ctrlKey;
59+
}
5560
}

src/text-input-autocomplete.directive.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ export class TextInputAutocompleteDirective implements OnDestroy {
3636
*/
3737
@Input() triggerCharacter = '@';
3838

39+
/**
40+
* An optional keyboard shortcut that will trigger the menu to appear
41+
*/
42+
@Input() keyboardShortcut: (event: KeyboardEvent) => boolean;
43+
3944
/**
4045
* The regular expression that will match the search text after the trigger character
4146
*/
@@ -88,6 +93,8 @@ export class TextInputAutocompleteDirective implements OnDestroy {
8893

8994
private menuHidden$ = new Subject();
9095

96+
private usingShortcut = false;
97+
9198
constructor(
9299
private componentFactoryResolver: ComponentFactoryResolver,
93100
private viewContainerRef: ViewContainerRef,
@@ -98,24 +105,42 @@ export class TextInputAutocompleteDirective implements OnDestroy {
98105
@HostListener('keypress', ['$event.key'])
99106
onKeypress(key: string) {
100107
if (key === this.triggerCharacter) {
108+
this.usingShortcut = false;
109+
this.showMenu();
110+
}
111+
}
112+
113+
@HostListener('keydown', ['$event'])
114+
onKeyDown(event: KeyboardEvent) {
115+
if (this.keyboardShortcut && this.keyboardShortcut(event)) {
116+
this.usingShortcut = true;
101117
this.showMenu();
118+
this.onChange('');
102119
}
103120
}
104121

105122
@HostListener('input', ['$event.target.value'])
106123
onChange(value: string) {
107124
if (this.menu) {
108-
if (value[this.menu.triggerCharacterPosition] !== this.triggerCharacter) {
125+
if (
126+
value[this.menu.triggerCharacterPosition] !== this.triggerCharacter &&
127+
!this.usingShortcut
128+
) {
109129
this.hideMenu();
110130
} else {
111131
const cursor = this.elm.nativeElement.selectionStart;
112132
if (cursor < this.menu.triggerCharacterPosition) {
113133
this.hideMenu();
114134
} else {
135+
if (this.usingShortcut && !this.menu) {
136+
value = this.triggerCharacter;
137+
}
138+
const offset = this.usingShortcut ? 0 : 1;
115139
const searchText = value.slice(
116-
this.menu.triggerCharacterPosition + 1,
140+
this.menu.triggerCharacterPosition + offset,
117141
cursor
118142
);
143+
119144
if (!searchText.match(this.searchRegexp)) {
120145
this.hideMenu();
121146
} else {

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { By } from '@angular/platform-browser';
2525
[findChoices]="findChoices"
2626
[getChoiceLabel]="getChoiceLabel"
2727
[triggerCharacter]="triggerCharacter"
28+
[keyboardShortcut]="shortcut"
2829
[searchRegexp]="searchRegexp"
2930
[menuComponent]="menuComponent"
3031
[closeMenuOnBlur]="closeMenuOnBlur"
@@ -46,6 +47,8 @@ class TestComponent {
4647
menuHidden = sinon.spy();
4748
choiceSelected = sinon.spy();
4849
closeMenuOnBlur = false;
50+
shortcut = (event: KeyboardEvent) =>
51+
(event.keyCode === 32 || event.code === '32') && event.ctrlKey;
4952
}
5053

5154
@Component({
@@ -130,6 +133,15 @@ describe('text-input-autocomplete directive', () => {
130133
expect(getMenu()).to.be.ok;
131134
});
132135

136+
it('should show a menu when the shortcut combo is typed', () => {
137+
const event = new KeyboardEvent('keydown', {
138+
code: '32',
139+
ctrlKey: true
140+
});
141+
textarea.nativeElement.dispatchEvent(event);
142+
expect(getMenu()).to.be.ok;
143+
});
144+
133145
it('should call find choices with the search text', () => {
134146
typeInTextarea('test @derp');
135147
expect(component.findChoices).to.have.been.calledWith('derp');

0 commit comments

Comments
 (0)