github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/app/components/multi-select-dropdown.js (about) 1 import Component from '@ember/component'; 2 import { action } from '@ember/object'; 3 import { computed as overridable } from 'ember-overridable-computed'; 4 import { run } from '@ember/runloop'; 5 import { classNames } from '@ember-decorators/component'; 6 import classic from 'ember-classic-decorator'; 7 8 const TAB = 9; 9 const ESC = 27; 10 const SPACE = 32; 11 const ARROW_UP = 38; 12 const ARROW_DOWN = 40; 13 14 @classic 15 @classNames('dropdown') 16 export default class MultiSelectDropdown extends Component { 17 @overridable(() => []) options; 18 @overridable(() => []) selection; 19 20 onSelect() {} 21 22 isOpen = false; 23 dropdown = null; 24 25 capture(dropdown) { 26 // It's not a good idea to grab a dropdown reference like this, but it's necessary 27 // in order to invoke dropdown.actions.close in traverseList as well as 28 // dropdown.actions.reposition when the label or selection length changes. 29 this.set('dropdown', dropdown); 30 } 31 32 didReceiveAttrs() { 33 const dropdown = this.dropdown; 34 if (this.isOpen && dropdown) { 35 run.scheduleOnce('afterRender', this, this.repositionDropdown); 36 } 37 } 38 39 repositionDropdown() { 40 this.dropdown.actions.reposition(); 41 } 42 43 @action 44 toggle({ key }) { 45 const newSelection = this.selection.slice(); 46 if (newSelection.includes(key)) { 47 newSelection.removeObject(key); 48 } else { 49 newSelection.addObject(key); 50 } 51 this.onSelect(newSelection); 52 } 53 54 @action 55 openOnArrowDown(dropdown, e) { 56 this.capture(dropdown); 57 58 if (!this.isOpen && e.keyCode === ARROW_DOWN) { 59 dropdown.actions.open(e); 60 e.preventDefault(); 61 } else if (this.isOpen && (e.keyCode === TAB || e.keyCode === ARROW_DOWN)) { 62 const optionsId = this.element.querySelector('.dropdown-trigger').getAttribute('aria-owns'); 63 const firstElement = document.querySelector(`#${optionsId} .dropdown-option`); 64 65 if (firstElement) { 66 firstElement.focus(); 67 e.preventDefault(); 68 } 69 } 70 } 71 72 @action 73 traverseList(option, e) { 74 if (e.keyCode === ESC) { 75 // Close the dropdown 76 const dropdown = this.dropdown; 77 if (dropdown) { 78 dropdown.actions.close(e); 79 // Return focus to the trigger so tab works as expected 80 const trigger = this.element.querySelector('.dropdown-trigger'); 81 if (trigger) trigger.focus(); 82 e.preventDefault(); 83 this.set('dropdown', null); 84 } 85 } else if (e.keyCode === ARROW_UP) { 86 // previous item 87 const prev = e.target.previousElementSibling; 88 if (prev) { 89 prev.focus(); 90 e.preventDefault(); 91 } 92 } else if (e.keyCode === ARROW_DOWN) { 93 // next item 94 const next = e.target.nextElementSibling; 95 if (next) { 96 next.focus(); 97 e.preventDefault(); 98 } 99 } else if (e.keyCode === SPACE) { 100 this.send('toggle', option); 101 e.preventDefault(); 102 } 103 } 104 }