1 import {onSelect} from "../services/dom";
5 * Provides some simple logic to create simple dropdown menus.
10 this.container = elem;
11 this.menu = elem.querySelector('.dropdown-menu, [dropdown-menu]');
12 this.moveMenu = elem.hasAttribute('dropdown-move-menu');
13 this.toggle = elem.querySelector('[dropdown-toggle]');
14 this.direction = (document.dir === 'rtl') ? 'right' : 'left';
15 this.body = document.body;
17 this.setupListeners();
23 this.menu.style.display = 'block';
24 this.menu.classList.add('anim', 'menuIn');
25 this.toggle.setAttribute('aria-expanded', 'true');
28 // Move to body to prevent being trapped within scrollable sections
29 this.rect = this.menu.getBoundingClientRect();
30 this.body.appendChild(this.menu);
31 this.menu.style.position = 'fixed';
32 if (this.direction === 'right') {
33 this.menu.style.right = `${(this.rect.right - this.rect.width)}px`;
35 this.menu.style.left = `${this.rect.left}px`;
37 this.menu.style.top = `${this.rect.top}px`;
38 this.menu.style.width = `${this.rect.width}px`;
41 // Set listener to hide on mouse leave or window click
42 this.menu.addEventListener('mouseleave', this.hide.bind(this));
43 window.addEventListener('click', event => {
44 if (!this.menu.contains(event.target)) {
49 // Focus on first input if existing
50 const input = this.menu.querySelector('input');
51 if (input !== null) input.focus();
55 const showEvent = new Event('show');
56 this.container.dispatchEvent(showEvent);
59 event.stopPropagation();
64 for (let dropdown of window.components.dropdown) {
70 this.menu.style.display = 'none';
71 this.menu.classList.remove('anim', 'menuIn');
72 this.toggle.setAttribute('aria-expanded', 'false');
74 this.menu.style.position = '';
75 this.menu.style[this.direction] = '';
76 this.menu.style.top = '';
77 this.menu.style.width = '';
78 this.container.appendChild(this.menu);
84 return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
88 const focusable = this.getFocusable();
89 const currentIndex = focusable.indexOf(document.activeElement);
90 let newIndex = currentIndex + 1;
91 if (newIndex >= focusable.length) {
95 focusable[newIndex].focus();
99 const focusable = this.getFocusable();
100 const currentIndex = focusable.indexOf(document.activeElement);
101 let newIndex = currentIndex - 1;
103 newIndex = focusable.length - 1;
106 focusable[newIndex].focus();
110 // Hide menu on option click
111 this.container.addEventListener('click', event => {
112 const possibleChildren = Array.from(this.menu.querySelectorAll('a'));
113 if (possibleChildren.includes(event.target)) {
118 onSelect(this.toggle, event => {
119 event.stopPropagation();
121 if (event instanceof KeyboardEvent) {
126 // Keyboard navigation
127 const keyboardNavigation = event => {
128 if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
130 event.preventDefault();
131 } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
132 this.focusPrevious();
133 event.preventDefault();
134 } else if (event.key === 'Escape') {
137 event.stopPropagation();
140 this.container.addEventListener('keydown', keyboardNavigation);
142 this.menu.addEventListener('keydown', keyboardNavigation);
145 // Hide menu on enter press or escape
146 this.menu.addEventListener('keydown ', event => {
147 if (event.key === 'Enter') {
148 event.preventDefault();
149 event.stopPropagation();
157 export default DropDown;