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); }