diff --git a/aio/src/app/shared/select/select.component.html b/aio/src/app/shared/select/select.component.html
index 2f333955c329c..7d339acb577f2 100644
--- a/aio/src/app/shared/select/select.component.html
+++ b/aio/src/app/shared/select/select.component.html
@@ -1,16 +1,29 @@
+
+ {{label}}{{title}}
+
\ No newline at end of file
diff --git a/aio/src/app/shared/select/select.component.spec.ts b/aio/src/app/shared/select/select.component.spec.ts
index b7ed8b5dc28ed..8fb07056f314c 100644
--- a/aio/src/app/shared/select/select.component.spec.ts
+++ b/aio/src/app/shared/select/select.component.spec.ts
@@ -112,8 +112,9 @@ describe('SelectComponent', () => {
});
it('should select the current option when enter is pressed', () => {
- const e = new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: 'Enter'});
- getOptions()[0].dispatchEvent(e);
+ element.componentInstance.currentOptionIdx = 0;
+ const debugBtnElement = fixture.debugElement.query(By.css('.form-select-button'));
+ debugBtnElement.triggerEventHandler('keydown', { bubbles: true, cancelable: true, key: ' ', preventDefault(){} });
fixture.detectChanges();
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
expect(getButton().textContent).toContain(options[0].title);
@@ -121,8 +122,9 @@ describe('SelectComponent', () => {
});
it('should select the current option when space is pressed', () => {
- const e = new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: ' '});
- getOptions()[0].dispatchEvent(e);
+ element.componentInstance.currentOptionIdx = 0;
+ const debugBtnElement = fixture.debugElement.query(By.css('.form-select-button'));
+ debugBtnElement.triggerEventHandler('keydown', { bubbles: true, cancelable: true, key: ' ', preventDefault(){} });
fixture.detectChanges();
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
expect(getButton().textContent).toContain(options[0].title);
@@ -142,8 +144,8 @@ describe('SelectComponent', () => {
});
it('should hide if the escape button is pressed', () => {
- const e = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Escape' });
- document.dispatchEvent(e);
+ const debugBtnElement = fixture.debugElement.query(By.css('.form-select-button'));
+ debugBtnElement.triggerEventHandler('keydown', { bubbles: true, cancelable: true, key: 'Escape', preventDefault(){} });
fixture.detectChanges();
expect(getOptionContainer()).toEqual(null);
});
diff --git a/aio/src/app/shared/select/select.component.ts b/aio/src/app/shared/select/select.component.ts
index a854deb6d4ab5..7add287037a72 100644
--- a/aio/src/app/shared/select/select.component.ts
+++ b/aio/src/app/shared/select/select.component.ts
@@ -1,4 +1,4 @@
-import { Component, ElementRef, EventEmitter, HostListener, Input, Output, OnInit } from '@angular/core';
+import { Component, ElementRef, EventEmitter, HostListener, Input, Output, OnInit, ViewChild } from '@angular/core';
export interface Option {
title: string;
@@ -23,8 +23,14 @@ export class SelectComponent implements OnInit {
@Input() disabled: boolean;
+ @ViewChild('listBox', { read: ElementRef }) listBox: ElementRef;
+
showOptions = false;
+ listBoxId = `aio-select-listbox-${Math.floor(Math.random() * 10000)}`;
+
+ currentOptionIdx = 0;
+
constructor(private hostElement: ElementRef) {}
ngOnInit() {
@@ -39,7 +45,8 @@ export class SelectComponent implements OnInit {
this.showOptions = false;
}
- select(option: Option, index: number) {
+ select(index: number) {
+ const option = this.options[index];
this.selected = option;
this.change.emit({option, index});
this.hideOptions();
@@ -53,8 +60,33 @@ export class SelectComponent implements OnInit {
}
}
- @HostListener('document:keydown.escape')
- onKeyDown() {
- this.hideOptions();
+ handleKeydown(event: KeyboardEvent) {
+ const runOrOpenOptions = (fn: () => void): void => {
+ if(!this.showOptions) {
+ this.showOptions = true;
+ } else {
+ fn();
+ }
+ }
+ switch(event.key) {
+ case 'ArrowDown':
+ runOrOpenOptions(() => this.currentOptionIdx = Math.min(this.currentOptionIdx + 1, (this.options?.length ?? 0) - 1));
+ break;
+ case 'ArrowUp':
+ runOrOpenOptions(() => this.currentOptionIdx = Math.max(this.currentOptionIdx - 1, 0));
+ break;
+ case 'Escape':
+ case 'Tab':
+ this.hideOptions();
+ break;
+ case 'Enter':
+ case 'Space':
+ case ' ':
+ runOrOpenOptions(() => this.select(this.currentOptionIdx));
+ break;
+ }
+ if(event.code !== 'Tab') {
+ event.preventDefault();
+ }
}
}
diff --git a/aio/src/styles/2-modules/select-menu/_select-menu-theme.scss b/aio/src/styles/2-modules/select-menu/_select-menu-theme.scss
index d31f410497d34..564e3e1f3d6f7 100644
--- a/aio/src/styles/2-modules/select-menu/_select-menu-theme.scss
+++ b/aio/src/styles/2-modules/select-menu/_select-menu-theme.scss
@@ -28,7 +28,7 @@
box-shadow: 0 16px 16px rgba(constants.$black, 0.24), 0 0 16px rgba(constants.$black, 0.12);
li {
- &:hover {
+ &:hover, &.current {
background-color: if($is-dark-theme, constants.$darkgray, constants.$blue-grey-50);
color: f($is-dark-theme, constants.$blue-grey-400, constants.$blue-grey-500);
}