mirror of
				https://github.com/BookStackApp/BookStack.git
				synced 2025-11-04 13:31:45 +03:00 
			
		
		
		
	- Intended to improve RTL support in the interface. - Also adds hebrew to language dropdown since that was missing. Related to #1794
		
			
				
	
	
		
			157 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import {onSelect} from "../services/dom";
 | 
						|
 | 
						|
/**
 | 
						|
 * Dropdown
 | 
						|
 * Provides some simple logic to create simple dropdown menus.
 | 
						|
 */
 | 
						|
class DropDown {
 | 
						|
 | 
						|
    constructor(elem) {
 | 
						|
        this.container = elem;
 | 
						|
        this.menu = elem.querySelector('.dropdown-menu, [dropdown-menu]');
 | 
						|
        this.moveMenu = elem.hasAttribute('dropdown-move-menu');
 | 
						|
        this.toggle = elem.querySelector('[dropdown-toggle]');
 | 
						|
        this.direction = (document.dir === 'rtl') ? 'right' : 'left';
 | 
						|
        this.body = document.body;
 | 
						|
        this.showing = false;
 | 
						|
        this.setupListeners();
 | 
						|
    }
 | 
						|
 | 
						|
    show(event = null) {
 | 
						|
        this.hideAll();
 | 
						|
 | 
						|
        this.menu.style.display = 'block';
 | 
						|
        this.menu.classList.add('anim', 'menuIn');
 | 
						|
        this.toggle.setAttribute('aria-expanded', 'true');
 | 
						|
 | 
						|
        if (this.moveMenu) {
 | 
						|
            // Move to body to prevent being trapped within scrollable sections
 | 
						|
            this.rect = this.menu.getBoundingClientRect();
 | 
						|
            this.body.appendChild(this.menu);
 | 
						|
            this.menu.style.position = 'fixed';
 | 
						|
            if (this.direction === 'right') {
 | 
						|
                this.menu.style.right = `${(this.rect.right - this.rect.width)}px`;
 | 
						|
            } else {
 | 
						|
                this.menu.style.left = `${this.rect.left}px`;
 | 
						|
            }
 | 
						|
            this.menu.style.top = `${this.rect.top}px`;
 | 
						|
            this.menu.style.width = `${this.rect.width}px`;
 | 
						|
        }
 | 
						|
 | 
						|
        // Set listener to hide on mouse leave or window click
 | 
						|
        this.menu.addEventListener('mouseleave', this.hide.bind(this));
 | 
						|
        window.addEventListener('click', event => {
 | 
						|
            if (!this.menu.contains(event.target)) {
 | 
						|
                this.hide();
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        // Focus on first input if existing
 | 
						|
        const input = this.menu.querySelector('input');
 | 
						|
        if (input !== null) input.focus();
 | 
						|
 | 
						|
        this.showing = true;
 | 
						|
 | 
						|
        const showEvent = new Event('show');
 | 
						|
        this.container.dispatchEvent(showEvent);
 | 
						|
 | 
						|
        if (event) {
 | 
						|
            event.stopPropagation();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    hideAll() {
 | 
						|
        for (let dropdown of window.components.dropdown) {
 | 
						|
            dropdown.hide();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    hide() {
 | 
						|
        this.menu.style.display = 'none';
 | 
						|
        this.menu.classList.remove('anim', 'menuIn');
 | 
						|
        this.toggle.setAttribute('aria-expanded', 'false');
 | 
						|
        if (this.moveMenu) {
 | 
						|
            this.menu.style.position = '';
 | 
						|
            this.menu.style[this.direction] = '';
 | 
						|
            this.menu.style.top = '';
 | 
						|
            this.menu.style.width = '';
 | 
						|
            this.container.appendChild(this.menu);
 | 
						|
        }
 | 
						|
        this.showing = false;
 | 
						|
    }
 | 
						|
 | 
						|
    getFocusable() {
 | 
						|
        return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
 | 
						|
    }
 | 
						|
 | 
						|
    focusNext() {
 | 
						|
        const focusable = this.getFocusable();
 | 
						|
        const currentIndex = focusable.indexOf(document.activeElement);
 | 
						|
        let newIndex = currentIndex + 1;
 | 
						|
        if (newIndex >= focusable.length) {
 | 
						|
            newIndex = 0;
 | 
						|
        }
 | 
						|
 | 
						|
        focusable[newIndex].focus();
 | 
						|
    }
 | 
						|
 | 
						|
    focusPrevious() {
 | 
						|
        const focusable = this.getFocusable();
 | 
						|
        const currentIndex = focusable.indexOf(document.activeElement);
 | 
						|
        let newIndex = currentIndex - 1;
 | 
						|
        if (newIndex < 0) {
 | 
						|
            newIndex = focusable.length - 1;
 | 
						|
        }
 | 
						|
 | 
						|
        focusable[newIndex].focus();
 | 
						|
    }
 | 
						|
 | 
						|
    setupListeners() {
 | 
						|
        // Hide menu on option click
 | 
						|
        this.container.addEventListener('click', event => {
 | 
						|
             const possibleChildren = Array.from(this.menu.querySelectorAll('a'));
 | 
						|
             if (possibleChildren.includes(event.target)) {
 | 
						|
                 this.hide();
 | 
						|
             }
 | 
						|
        });
 | 
						|
 | 
						|
        onSelect(this.toggle, event => {
 | 
						|
            event.stopPropagation();
 | 
						|
            this.show(event);
 | 
						|
            if (event instanceof KeyboardEvent) {
 | 
						|
                this.focusNext();
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        // Keyboard navigation
 | 
						|
        const keyboardNavigation = event => {
 | 
						|
            if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
 | 
						|
                this.focusNext();
 | 
						|
                event.preventDefault();
 | 
						|
            } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
 | 
						|
                this.focusPrevious();
 | 
						|
                event.preventDefault();
 | 
						|
            } else if (event.key === 'Escape') {
 | 
						|
                this.hide();
 | 
						|
                this.toggle.focus();
 | 
						|
                event.stopPropagation();
 | 
						|
            }
 | 
						|
        };
 | 
						|
        this.container.addEventListener('keydown', keyboardNavigation);
 | 
						|
        if (this.moveMenu) {
 | 
						|
            this.menu.addEventListener('keydown', keyboardNavigation);
 | 
						|
        }
 | 
						|
 | 
						|
        // Hide menu on enter press or escape
 | 
						|
        this.menu.addEventListener('keydown ', event => {
 | 
						|
            if (event.key === 'Enter') {
 | 
						|
                event.preventDefault();
 | 
						|
                event.stopPropagation();
 | 
						|
                this.hide();
 | 
						|
            }
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
export default DropDown; |