2019-01-11 23:41:56 +00:00
|
|
|
import Component from '@ember/component';
|
|
|
|
import { computed } from '@ember/object';
|
2019-01-17 00:28:13 +00:00
|
|
|
import { run } from '@ember/runloop';
|
2019-01-11 23:41:56 +00:00
|
|
|
|
2019-01-15 01:59:41 +00:00
|
|
|
const TAB = 9;
|
|
|
|
const ESC = 27;
|
|
|
|
const SPACE = 32;
|
|
|
|
const ARROW_UP = 38;
|
|
|
|
const ARROW_DOWN = 40;
|
|
|
|
|
2019-01-11 23:41:56 +00:00
|
|
|
export default Component.extend({
|
|
|
|
classNames: ['dropdown'],
|
|
|
|
|
|
|
|
options: computed(() => []),
|
|
|
|
selection: computed(() => []),
|
|
|
|
|
|
|
|
onSelect() {},
|
|
|
|
|
2019-01-15 01:59:41 +00:00
|
|
|
isOpen: false,
|
|
|
|
dropdown: null,
|
|
|
|
|
2019-01-17 00:28:13 +00:00
|
|
|
capture(dropdown) {
|
|
|
|
// It's not a good idea to grab a dropdown reference like this, but it's necessary
|
|
|
|
// in order to invoke dropdown.actions.close in traverseList as well as
|
|
|
|
// dropdown.actions.reposition when the label or selection length changes.
|
|
|
|
this.set('dropdown', dropdown);
|
|
|
|
},
|
|
|
|
|
|
|
|
didReceiveAttrs() {
|
2019-03-26 07:46:44 +00:00
|
|
|
const dropdown = this.dropdown;
|
|
|
|
if (this.isOpen && dropdown) {
|
2019-01-17 00:28:13 +00:00
|
|
|
run.scheduleOnce('afterRender', () => {
|
|
|
|
dropdown.actions.reposition();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-01-11 23:41:56 +00:00
|
|
|
actions: {
|
|
|
|
toggle({ key }) {
|
2019-03-26 07:46:44 +00:00
|
|
|
const newSelection = this.selection.slice();
|
2019-01-11 23:41:56 +00:00
|
|
|
if (newSelection.includes(key)) {
|
|
|
|
newSelection.removeObject(key);
|
|
|
|
} else {
|
|
|
|
newSelection.addObject(key);
|
|
|
|
}
|
2019-03-26 07:46:44 +00:00
|
|
|
this.onSelect(newSelection);
|
2019-01-11 23:41:56 +00:00
|
|
|
},
|
2019-01-15 01:59:41 +00:00
|
|
|
|
|
|
|
openOnArrowDown(dropdown, e) {
|
2019-01-17 00:28:13 +00:00
|
|
|
this.capture(dropdown);
|
2019-01-15 01:59:41 +00:00
|
|
|
|
2019-03-26 07:46:44 +00:00
|
|
|
if (!this.isOpen && e.keyCode === ARROW_DOWN) {
|
2019-01-15 01:59:41 +00:00
|
|
|
dropdown.actions.open(e);
|
|
|
|
e.preventDefault();
|
2019-03-26 07:46:44 +00:00
|
|
|
} else if (this.isOpen && (e.keyCode === TAB || e.keyCode === ARROW_DOWN)) {
|
2019-01-15 01:59:41 +00:00
|
|
|
const optionsId = this.element.querySelector('.dropdown-trigger').getAttribute('aria-owns');
|
|
|
|
const firstElement = document.querySelector(`#${optionsId} .dropdown-option`);
|
|
|
|
|
|
|
|
if (firstElement) {
|
|
|
|
firstElement.focus();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
traverseList(option, e) {
|
|
|
|
if (e.keyCode === ESC) {
|
|
|
|
// Close the dropdown
|
2019-03-26 07:46:44 +00:00
|
|
|
const dropdown = this.dropdown;
|
2019-01-15 01:59:41 +00:00
|
|
|
if (dropdown) {
|
|
|
|
dropdown.actions.close(e);
|
|
|
|
// Return focus to the trigger so tab works as expected
|
|
|
|
const trigger = this.element.querySelector('.dropdown-trigger');
|
|
|
|
if (trigger) trigger.focus();
|
|
|
|
e.preventDefault();
|
|
|
|
this.set('dropdown', null);
|
|
|
|
}
|
|
|
|
} else if (e.keyCode === ARROW_UP) {
|
|
|
|
// previous item
|
|
|
|
const prev = e.target.previousElementSibling;
|
|
|
|
if (prev) {
|
|
|
|
prev.focus();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
} else if (e.keyCode === ARROW_DOWN) {
|
|
|
|
// next item
|
|
|
|
const next = e.target.nextElementSibling;
|
|
|
|
if (next) {
|
|
|
|
next.focus();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
} else if (e.keyCode === SPACE) {
|
|
|
|
this.send('toggle', option);
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
},
|
2019-01-11 23:41:56 +00:00
|
|
|
},
|
|
|
|
});
|