UNPKG

rabbit-design

Version:

A lightweight UI plugin library written in TypeScript and based on JavaScript

240 lines (195 loc) 7.92 kB
import PREFIX from '../prefix'; import { $el, bind, createElem, getStrTypeAttr, removeAttrs, setCss, setHtml } from '../../dom-utils'; import { CssTransition, warn, _Popper } from '../../mixins'; import { type, validComps } from '../../utils'; // 通过点击事件冒泡的方式处理单击下拉菜单项隐藏菜单 function handleDropdownItemClickHidden(): void { bind(document, 'click', (e: any) => { const { target } = e; // 获取点击的目标元素名 const tagName = target.tagName.toLocaleLowerCase(); if (tagName === 'r-dropdown-item') { // 是否为禁用项 if (target.getAttribute('disabled') === '') return; // 获取菜单项的最外层容器 div.rab-select-dropdown const dropdownMenu = target.parentElement.parentElement; // 设置为隐藏状态 dropdownMenu.dataset.dropdownVisable = 'false'; CssTransition(dropdownMenu, { inOrOut: 'out', leaveCls: 'transition-drop-leave', rmCls: true, timeout: 280 }); } }); } interface DropdownEvents { onClick: (index?: number) => void; // 点击菜单项时触发,返回 r-dropdown-item 索引值 } interface Config { config( el: string ): { events: ({ onClick }: DropdownEvents) => void; }; } const defalutDpdDelay = 100; let SHOWTIMER: any; class Dropdown implements Config { readonly VERSION: string; readonly COMPONENTS: NodeListOf<Element>; constructor() { this.VERSION = 'v1.0'; this.COMPONENTS = $el('r-dropdown', { all: true }); this._create(this.COMPONENTS); handleDropdownItemClickHidden(); } public config( el: string ): { events({ onClick }: { onClick: (index?: number) => void }): void; } { const target = $el(el); validComps(target, 'dropdown'); return { events({ onClick }) { const children = target.querySelectorAll('r-dropdown-item'); children.forEach((child: Element, index: number) => { bind(child, 'click', () => { child.getAttribute('disabled') !== '' ? onClick && type.isFn(onClick, index) : undefined; }); }); } }; } private _create(COMPONENTS: NodeListOf<Element>): void { COMPONENTS.forEach((node) => { // 判断是否由两个子节点组成 if (node.childElementCount > 2) { warn( 'The content of a component dropdown can only be composed of two element nodes, the first being the reference element and the second being the drop-down item' ); } // 将第一个子元素作为宿主元素 const refElm: Element | null = node.firstElementChild; // 最后一个子元素即菜单项 const menuItem: Element | null = node.lastElementChild; // 清空旧内容,防止获取的元素不正确 setHtml(node, ''); const DropdownRef = this._setReferenceElm(node, refElm); const DropdownMenu = this._setMenuItem(node, menuItem); this._handleTrigger(node, DropdownRef, DropdownMenu); this._setTransformOrigin(node, DropdownMenu); removeAttrs(node, ['trigger', 'placement']); }); } private _setReferenceElm(node: Element, refElm: Element | null): HTMLElement { const DropdownRel = createElem('div'); DropdownRel.className = `${PREFIX.dropdown}-rel`; refElm ? DropdownRel.appendChild(refElm) : ''; node.appendChild(DropdownRel); return DropdownRel; } private _setMenuItem(node: Element, menuItem: Element | null): HTMLElement { const DropdownMenu = createElem('div'); DropdownMenu.className = 'rab-select-dropdown'; this._initVisable(DropdownMenu); menuItem ? DropdownMenu.appendChild(menuItem) : ''; node.appendChild(DropdownMenu); setCss(menuItem, 'display', 'block'); return DropdownMenu; } private _initVisable(dpdMenu: HTMLElement): void { setCss(dpdMenu, 'display', 'none'); dpdMenu.dataset.dropdownVisable = 'false'; } private _setTransformOrigin(parent: Element, dpdMenu: HTMLElement): void { const { placement } = this._attrs(parent); // 根据 placement 设置源方向。 // top 开头、right-end、left-end 的位置设置源方向为 center-bottom,反之。 // left 和 right 开头的则无需设置。 if (/^top|right-end|left-end/.test(placement)) { setCss(dpdMenu, 'transformOrigin', 'center bottom'); } else if (/^bottom|right-start|left-start/.test(placement)) { setCss(dpdMenu, 'transformOrigin', 'center top'); } // TODO: 根据 popper 的方向动态改变源方向 // dpdMenu.dataset.popperPlacement; } private _handleTrigger(parent: Element, dpdRef: HTMLElement, dpdMenu: HTMLElement): void { const { trigger, placement } = this._attrs(parent); const setPopper = () => _Popper._newCreatePopper(dpdRef, dpdMenu, placement, 0); const show = () => { setPopper(); dpdMenu.dataset.dropdownVisable = 'true'; CssTransition(dpdMenu, { inOrOut: 'in', enterCls: 'transition-drop-enter', rmCls: true, timeout: 300 }); }; const hidden = () => { if (dpdMenu.dataset.dropdownVisable === 'true') { dpdMenu.dataset.dropdownVisable = 'false'; CssTransition(dpdMenu, { inOrOut: 'out', leaveCls: 'transition-drop-leave', rmCls: true, timeout: 280 }); } }; // 通过点击宿主元素的次数判断是否显示或隐藏菜单项 const clicksIsVisable = (clicks: number) => (clicks % 2 == 0 ? hidden() : show()); if (trigger === 'hover') { bind(parent, 'mouseenter', () => { SHOWTIMER = setTimeout(() => { show(); }, defalutDpdDelay); }); bind(parent, 'mouseleave', () => { clearTimeout(SHOWTIMER); hidden(); }); } else if (trigger === 'click') { // 初始当前的点击次数 let currentClicks = 1; bind(dpdRef, 'click', () => clicksIsVisable(currentClicks++)); bind(dpdRef, 'focusin', show); bind(dpdRef, 'focusout', () => { currentClicks++; hidden(); }); } else if (trigger === 'contextMenu') { // 初始当前的右击次数 let currentRightClick = 1; bind(dpdRef, 'contextmenu', (e: any) => { e.preventDefault(); clicksIsVisable(currentRightClick++); }); bind(dpdRef, 'focusout', () => { currentRightClick++; hidden(); }); } } private _attrs(node: Element) { return { trigger: getStrTypeAttr(node, 'trigger', 'hover'), placement: getStrTypeAttr(node, 'placement', 'bottom') }; } } export default Dropdown;