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