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.body = document.body;
16 this.setupListeners();
22 this.menu.style.display = 'block';
23 this.menu.classList.add('anim', 'menuIn');
24 this.toggle.setAttribute('aria-expanded', 'true');
27 // Move to body to prevent being trapped within scrollable sections
28 this.rect = this.menu.getBoundingClientRect();
29 this.body.appendChild(this.menu);
30 this.menu.style.position = 'fixed';
31 this.menu.style.left = `${this.rect.left}px`;
32 this.menu.style.top = `${this.rect.top}px`;
33 this.menu.style.width = `${this.rect.width}px`;
36 // Set listener to hide on mouse leave or window click
37 this.menu.addEventListener('mouseleave', this.hide.bind(this));
38 window.addEventListener('click', event => {
39 if (!this.menu.contains(event.target)) {
44 // Focus on first input if existing
45 const input = this.menu.querySelector('input');
46 if (input !== null) input.focus();
50 const showEvent = new Event('show');
51 this.container.dispatchEvent(showEvent);
54 event.stopPropagation();
59 for (let dropdown of window.components.dropdown) {
65 this.menu.style.display = 'none';
66 this.menu.classList.remove('anim', 'menuIn');
67 this.toggle.setAttribute('aria-expanded', 'false');
69 this.menu.style.position = '';
70 this.menu.style.left = '';
71 this.menu.style.top = '';
72 this.menu.style.width = '';
73 this.container.appendChild(this.menu);
79 return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
83 const focusable = this.getFocusable();
84 const currentIndex = focusable.indexOf(document.activeElement);
85 let newIndex = currentIndex + 1;
86 if (newIndex >= focusable.length) {
90 focusable[newIndex].focus();
94 const focusable = this.getFocusable();
95 const currentIndex = focusable.indexOf(document.activeElement);
96 let newIndex = currentIndex - 1;
98 newIndex = focusable.length - 1;
101 focusable[newIndex].focus();
105 // Hide menu on option click
106 this.container.addEventListener('click', event => {
107 const possibleChildren = Array.from(this.menu.querySelectorAll('a'));
108 if (possibleChildren.includes(event.target)) {
113 onSelect(this.toggle, event => {
114 event.stopPropagation();
116 if (event instanceof KeyboardEvent) {
121 // Keyboard navigation
122 const keyboardNavigation = event => {
123 if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
125 event.preventDefault();
126 } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
127 this.focusPrevious();
128 event.preventDefault();
129 } else if (event.key === 'Escape') {
132 event.stopPropagation();
135 this.container.addEventListener('keydown', keyboardNavigation);
137 this.menu.addEventListener('keydown', keyboardNavigation);
140 // Hide menu on enter press or escape
141 this.menu.addEventListener('keydown ', event => {
142 if (event.key === 'Enter') {
143 event.preventDefault();
144 event.stopPropagation();
152 export default DropDown;