1 import {onSelect} from "../services/dom";
5 * Provides some simple logic to create simple dropdown menus.
11 this.container = this.$el;
12 this.menu = this.$refs.menu;
13 this.toggle = this.$refs.toggle;
14 this.moveMenu = this.$opts.moveMenu;
16 this.direction = (document.dir === 'rtl') ? 'right' : 'left';
17 this.body = document.body;
19 this.setupListeners();
20 this.hide = this.hide.bind(this);
26 this.menu.style.display = 'block';
27 this.menu.classList.add('anim', 'menuIn');
28 this.toggle.setAttribute('aria-expanded', 'true');
31 // Move to body to prevent being trapped within scrollable sections
32 this.rect = this.menu.getBoundingClientRect();
33 this.body.appendChild(this.menu);
34 this.menu.style.position = 'fixed';
35 if (this.direction === 'right') {
36 this.menu.style.right = `${(this.rect.right - this.rect.width)}px`;
38 this.menu.style.left = `${this.rect.left}px`;
40 this.menu.style.top = `${this.rect.top}px`;
41 this.menu.style.width = `${this.rect.width}px`;
44 // Set listener to hide on mouse leave or window click
45 this.menu.addEventListener('mouseleave', this.hide.bind(this));
46 window.addEventListener('click', event => {
47 if (!this.menu.contains(event.target)) {
52 // Focus on first input if existing
53 const input = this.menu.querySelector('input');
54 if (input !== null) input.focus();
58 const showEvent = new Event('show');
59 this.container.dispatchEvent(showEvent);
62 event.stopPropagation();
67 for (let dropdown of window.components.dropdown) {
73 this.menu.style.display = 'none';
74 this.menu.classList.remove('anim', 'menuIn');
75 this.toggle.setAttribute('aria-expanded', 'false');
77 this.menu.style.position = '';
78 this.menu.style[this.direction] = '';
79 this.menu.style.top = '';
80 this.menu.style.width = '';
81 this.container.appendChild(this.menu);
87 return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
91 const focusable = this.getFocusable();
92 const currentIndex = focusable.indexOf(document.activeElement);
93 let newIndex = currentIndex + 1;
94 if (newIndex >= focusable.length) {
98 focusable[newIndex].focus();
102 const focusable = this.getFocusable();
103 const currentIndex = focusable.indexOf(document.activeElement);
104 let newIndex = currentIndex - 1;
106 newIndex = focusable.length - 1;
109 focusable[newIndex].focus();
113 // Hide menu on option click
114 this.container.addEventListener('click', event => {
115 const possibleChildren = Array.from(this.menu.querySelectorAll('a'));
116 if (possibleChildren.includes(event.target)) {
121 onSelect(this.toggle, event => {
122 event.stopPropagation();
124 if (event instanceof KeyboardEvent) {
129 // Keyboard navigation
130 const keyboardNavigation = event => {
131 if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
133 event.preventDefault();
134 } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
135 this.focusPrevious();
136 event.preventDefault();
137 } else if (event.key === 'Escape') {
140 event.stopPropagation();
143 this.container.addEventListener('keydown', keyboardNavigation);
145 this.menu.addEventListener('keydown', keyboardNavigation);
148 // Hide menu on enter press or escape
149 this.menu.addEventListener('keydown ', event => {
150 if (event.key === 'Enter') {
151 event.preventDefault();
152 event.stopPropagation();
160 export default DropDown;