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  }