<script> import Clickoutside from 'element-ui/src/utils/clickoutside'; import Emitter from 'element-ui/src/mixins/emitter'; import Migrating from 'element-ui/src/mixins/migrating'; import ElButton from 'element-ui/packages/button'; import ElButtonGroup from 'element-ui/packages/button-group'; import { generateId } from 'element-ui/src/utils/util'; export default { name: 'ElDropdown', componentName: 'ElDropdown', mixins: [Emitter, Migrating], directives: { Clickoutside }, components: { ElButton, ElButtonGroup }, provide() { return { dropdown: this }; }, props: { trigger: { type: String, default: 'hover' }, type: String, size: { type: String, default: '' }, splitButton: Boolean, hideOnClick: { type: Boolean, default: true }, placement: { type: String, default: 'bottom-end' }, visibleArrow: { default: true }, showTimeout: { type: Number, default: 250 }, hideTimeout: { type: Number, default: 150 }, tabindex: { type: Number, default: 0 }, disabled: { type: Boolean, default: false } }, data() { return { timeout: null, visible: false, triggerElm: null, menuItems: null, menuItemsArray: null, dropdownElm: null, focusing: false, listId: `dropdown-menu-${generateId()}` }; }, computed: { dropdownSize() { return this.size || (this.$ELEMENT || {}).size; } }, mounted() { this.$on('menu-item-click', this.handleMenuItemClick); }, watch: { visible(val) { this.broadcast('ElDropdownMenu', 'visible', val); this.$emit('visible-change', val); }, focusing(val) { const selfDefine = this.$el.querySelector('.el-dropdown-selfdefine'); if (selfDefine) { // 鑷畾涔� if (val) { selfDefine.className += ' focusing'; } else { selfDefine.className = selfDefine.className.replace('focusing', ''); } } } }, methods: { getMigratingConfig() { return { props: { 'menu-align': 'menu-align is renamed to placement.' } }; }, show() { if (this.disabled) return; clearTimeout(this.timeout); this.timeout = setTimeout(() => { this.visible = true; }, this.trigger === 'click' ? 0 : this.showTimeout); }, hide() { if (this.disabled) return; this.removeTabindex(); if (this.tabindex >= 0) { this.resetTabindex(this.triggerElm); } clearTimeout(this.timeout); this.timeout = setTimeout(() => { this.visible = false; }, this.trigger === 'click' ? 0 : this.hideTimeout); }, handleClick() { if (this.disabled) return; if (this.visible) { this.hide(); } else { this.show(); } }, handleTriggerKeyDown(ev) { const keyCode = ev.keyCode; if ([38, 40].indexOf(keyCode) > -1) { // up/down this.removeTabindex(); this.resetTabindex(this.menuItems[0]); this.menuItems[0].focus(); ev.preventDefault(); ev.stopPropagation(); } else if (keyCode === 13) { // space enter閫変腑 this.handleClick(); } else if ([9, 27].indexOf(keyCode) > -1) { // tab || esc this.hide(); } }, handleItemKeyDown(ev) { const keyCode = ev.keyCode; const target = ev.target; const currentIndex = this.menuItemsArray.indexOf(target); const max = this.menuItemsArray.length - 1; let nextIndex; if ([38, 40].indexOf(keyCode) > -1) { // up/down if (keyCode === 38) { // up nextIndex = currentIndex !== 0 ? currentIndex - 1 : 0; } else { // down nextIndex = currentIndex < max ? currentIndex + 1 : max; } this.removeTabindex(); this.resetTabindex(this.menuItems[nextIndex]); this.menuItems[nextIndex].focus(); ev.preventDefault(); ev.stopPropagation(); } else if (keyCode === 13) { // enter閫変腑 this.triggerElmFocus(); target.click(); if (this.hideOnClick) { // click鍏抽棴 this.visible = false; } } else if ([9, 27].indexOf(keyCode) > -1) { // tab // esc this.hide(); this.triggerElmFocus(); } }, resetTabindex(ele) { // 涓嬫tab鏃剁粍浠惰仛鐒﹀厓绱� this.removeTabindex(); ele.setAttribute('tabindex', '0'); // 涓嬫鏈熸湜鐨勮仛鐒﹀厓绱� }, removeTabindex() { this.triggerElm.setAttribute('tabindex', '-1'); this.menuItemsArray.forEach((item) => { item.setAttribute('tabindex', '-1'); }); }, initAria() { this.dropdownElm.setAttribute('id', this.listId); this.triggerElm.setAttribute('aria-haspopup', 'list'); this.triggerElm.setAttribute('aria-controls', this.listId); if (!this.splitButton) { // 鑷畾涔� this.triggerElm.setAttribute('role', 'button'); this.triggerElm.setAttribute('tabindex', this.tabindex); this.triggerElm.setAttribute('class', (this.triggerElm.getAttribute('class') || '') + ' el-dropdown-selfdefine'); // 鎺у埗 } }, initEvent() { let { trigger, show, hide, handleClick, splitButton, handleTriggerKeyDown, handleItemKeyDown } = this; this.triggerElm = splitButton ? this.$refs.trigger.$el : this.$slots.default[0].elm; let dropdownElm = this.dropdownElm; this.triggerElm.addEventListener('keydown', handleTriggerKeyDown); // triggerElm keydown dropdownElm.addEventListener('keydown', handleItemKeyDown, true); // item keydown // 鎺у埗鑷畾涔夊厓绱犵殑鏍峰紡 if (!splitButton) { this.triggerElm.addEventListener('focus', () => { this.focusing = true; }); this.triggerElm.addEventListener('blur', () => { this.focusing = false; }); this.triggerElm.addEventListener('click', () => { this.focusing = false; }); } if (trigger === 'hover') { this.triggerElm.addEventListener('mouseenter', show); this.triggerElm.addEventListener('mouseleave', hide); dropdownElm.addEventListener('mouseenter', show); dropdownElm.addEventListener('mouseleave', hide); } else if (trigger === 'click') { this.triggerElm.addEventListener('click', handleClick); } }, handleMenuItemClick(command, instance) { if (this.hideOnClick) { this.visible = false; } this.$emit('command', command, instance); }, triggerElmFocus() { this.triggerElm.focus && this.triggerElm.focus(); }, initDomOperation() { this.dropdownElm = this.popperElm; this.menuItems = this.dropdownElm.querySelectorAll("[tabindex='-1']"); this.menuItemsArray = [].slice.call(this.menuItems); this.initEvent(); this.initAria(); } }, render(h) { let { hide, splitButton, type, dropdownSize, disabled } = this; const handleMainButtonClick = (event) => { this.$emit('click', event); hide(); }; let triggerElm = null; if (splitButton) { triggerElm = <el-button-group> <el-button type={type} size={dropdownSize} nativeOn-click={handleMainButtonClick} disabled={disabled}> {this.$slots.default} </el-button> <el-button ref="trigger" type={type} size={dropdownSize} class="el-dropdown__caret-button" disabled={disabled}> <i class="el-dropdown__icon el-icon-arrow-down"></i> </el-button> </el-button-group>; } else { triggerElm = this.$slots.default; const vnodeData = triggerElm[0].data || {}; let { attrs = {} } = vnodeData; if (disabled && !attrs.disabled) { attrs.disabled = true; vnodeData.attrs = attrs; } } const menuElm = disabled ? null : this.$slots.dropdown; return ( <div class="el-dropdown" v-clickoutside={hide} aria-disabled={disabled}> {triggerElm} {menuElm} </div> ); } }; </script>