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