import getFloatedTargetPos from '../utils/getFloatedTargetPos' import supportDom from '../decorators/supportDom' import { isFunction, toPixel, throttle } from '../utils'
@supportDom export default class Dropdown {
constructor(dom, options = {}) { this.dom = dom this.options = options this.isMenuVisible = false this.place = null this.align = null this.defaultTextNode = this.getDefaultTextNode(dom, options.textIndex) this.defaultText = this.defaultTextNode ? this.defaultTextNode.textContent.trim() : '' this.backdropMode = options.backdropMode || 'auto' this.init() } restoreText() { if (this.defaultTextNode) { this.defaultTextNode.textContent = this.defaultText } } setText(text) { if (this.defaultTextNode) { this.defaultTextNode.textContent = text } } getDefaultTextNode(dom, index = 0) { return dom.childNodes[index] } init() { if (! this.dom) { throw new Error('dom is missing in Dropdown class') } this.id = this.dom.dataset.target this.menu = document.querySelector(`[data-dropdown-menu="${this.id}"]`) if (! this.menu) { throw new Error(`menu ${this.id} is missing in Dropdown class`) } this.place = this.menu.dataset.place || 'bottom' this.align = this.menu.dataset.align this.menu.remove() this.addEvents() } restoreMenuAttrs() { const { menu, place, align } = this if (place) { menu.dataset.place = place } if (align) { menu.dataset.align = align } } hideMenu(immediate = false) { const { menu } = this menu.style.transform = 'scale(.8)' menu.style.opacity = 0 if (immediate) { menu.remove() } else { setTimeout(() => menu.remove(), 300) } this.restoreMenuAttrs() this.isMenuVisible = false } showMenu() { const { menu } = this menu.style.display = 'block' menu.style.opacity = 0 menu.style.transform = 'scale(.8)' document.body.appendChild(menu) this.adjustMenuPos() menu.style.transform = 'scale(1)' menu.style.opacity = 1 this.isMenuVisible = true } toggleMenu() { return this.isMenuVisible ? this.hideMenu() : this.showMenu() } adjustMenuPos() { const { menu, dom } = this const { dataset } = menu const offset = ('offset' in dataset) ? parseInt(dataset.offset, 10) : 14 const offsetLeft = ('offsetLeft' in dataset) ? parseInt(dataset.offsetLeft, 10) : 0 const offsetTop = ('offsetTop' in dataset) ? parseInt(dataset.offsetTop, 10) : 0 const { pos, place, align } = getFloatedTargetPos({ src: dom, target: menu, place: this.place, align: this.align, offset, offsetLeft, offsetTop }) dataset.place = place dataset.align = align menu.style.left = toPixel(pos.left) menu.style.top = toPixel(pos.top) } addEvents() { const { menuMouseOver, menuMouseLeave, menuClick } = this.options if (isFunction(menuMouseOver)) { this.addEvent(this.menu, 'mouseover', event => menuMouseOver(event)) } if (isFunction(menuMouseLeave)) { this.addEvent(this.menu, 'mouseleave', event => menuMouseLeave(event)) } if (isFunction(menuClick)) { this.addEvent(this.menu, 'click', event => { const { dataset } = event.target if ('dropdownItem' in dataset) { menuClick(event) } }) } this.addEvent(this.dom, 'click', () => this.toggleMenu()) this.addEvent(document, 'click', event => { if (! this.isMenuVisible) { return } if (this.backdropMode === 'manual') { return } // is backdrop if ((event.target !== this.dom) && (! this.dom.contains(event.target))) { this.hideMenu() } }) this.addEvent(window, 'resize', throttle(() => { if (! this.isMenuVisible) { return } this.adjustMenuPos() }, 300)) } destroy() { // prevent next re-bind error const { menu } = this if (menu) { this.hideMenu(true) document.body.appendChild(menu) } }
}