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;
15 this.bubbleEscapes = this.$opts.bubbleEscapes === 'true';
17 this.direction = (document.dir === 'rtl') ? 'right' : 'left';
18 this.body = document.body;
20 this.setupListeners();
21 this.hide = this.hide.bind(this);
27 this.menu.style.display = 'block';
28 this.menu.classList.add('anim', 'menuIn');
29 this.toggle.setAttribute('aria-expanded', 'true');
32 // Move to body to prevent being trapped within scrollable sections
33 this.rect = this.menu.getBoundingClientRect();
34 this.body.appendChild(this.menu);
35 this.menu.style.position = 'fixed';
36 if (this.direction === 'right') {
37 this.menu.style.right = `${(this.rect.right - this.rect.width)}px`;
39 this.menu.style.left = `${this.rect.left}px`;
41 this.menu.style.top = `${this.rect.top}px`;
42 this.menu.style.width = `${this.rect.width}px`;
45 // Set listener to hide on mouse leave or window click
46 this.menu.addEventListener('mouseleave', this.hide.bind(this));
47 window.addEventListener('click', event => {
48 if (!this.menu.contains(event.target)) {
53 // Focus on first input if existing
54 const input = this.menu.querySelector('input');
55 if (input !== null) input.focus();
59 const showEvent = new Event('show');
60 this.container.dispatchEvent(showEvent);
63 event.stopPropagation();
68 for (let dropdown of window.components.dropdown) {
74 this.menu.style.display = 'none';
75 this.menu.classList.remove('anim', 'menuIn');
76 this.toggle.setAttribute('aria-expanded', 'false');
78 this.menu.style.position = '';
79 this.menu.style[this.direction] = '';
80 this.menu.style.top = '';
81 this.menu.style.width = '';
82 this.container.appendChild(this.menu);
88 return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
92 const focusable = this.getFocusable();
93 const currentIndex = focusable.indexOf(document.activeElement);
94 let newIndex = currentIndex + 1;
95 if (newIndex >= focusable.length) {
99 focusable[newIndex].focus();
103 const focusable = this.getFocusable();
104 const currentIndex = focusable.indexOf(document.activeElement);
105 let newIndex = currentIndex - 1;
107 newIndex = focusable.length - 1;
110 focusable[newIndex].focus();
114 // Hide menu on option click
115 this.container.addEventListener('click', event => {
116 const possibleChildren = Array.from(this.menu.querySelectorAll('a'));
117 if (possibleChildren.includes(event.target)) {
122 onSelect(this.toggle, event => {
123 event.stopPropagation();
125 if (event instanceof KeyboardEvent) {
130 // Keyboard navigation
131 const keyboardNavigation = event => {
132 if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
134 event.preventDefault();
135 } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
136 this.focusPrevious();
137 event.preventDefault();
138 } else if (event.key === 'Escape') {
141 if (!this.bubbleEscapes) {
142 event.stopPropagation();
146 this.container.addEventListener('keydown', keyboardNavigation);
148 this.menu.addEventListener('keydown', keyboardNavigation);
151 // Hide menu on enter press or escape
152 this.menu.addEventListener('keydown ', event => {
153 if (event.key === 'Enter') {
154 event.preventDefault();
155 event.stopPropagation();
163 export default DropDown;