first
This commit is contained in:
8
packages/dropdown/index.js
Normal file
8
packages/dropdown/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import ElDropdown from './src/dropdown';
|
||||
|
||||
/* istanbul ignore next */
|
||||
ElDropdown.install = function(Vue) {
|
||||
Vue.component(ElDropdown.name, ElDropdown);
|
||||
};
|
||||
|
||||
export default ElDropdown;
|
37
packages/dropdown/src/dropdown-item.vue
Normal file
37
packages/dropdown/src/dropdown-item.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<li
|
||||
class="el-dropdown-menu__item"
|
||||
:class="{
|
||||
'is-disabled': disabled,
|
||||
'el-dropdown-menu__item--divided': divided
|
||||
}"
|
||||
@click="handleClick"
|
||||
:aria-disabled="disabled"
|
||||
:tabindex="disabled ? null : -1"
|
||||
>
|
||||
<i :class="icon" v-if="icon"></i>
|
||||
<slot></slot>
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
import Emitter from 'element-ui/src/mixins/emitter';
|
||||
|
||||
export default {
|
||||
name: 'ElDropdownItem',
|
||||
|
||||
mixins: [Emitter],
|
||||
|
||||
props: {
|
||||
command: {},
|
||||
disabled: Boolean,
|
||||
divided: Boolean,
|
||||
icon: String
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick(e) {
|
||||
this.dispatch('ElDropdown', 'menu-item-click', [this.command, this]);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
63
packages/dropdown/src/dropdown-menu.vue
Normal file
63
packages/dropdown/src/dropdown-menu.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-top" @after-leave="doDestroy">
|
||||
<ul class="el-dropdown-menu el-popper" :class="[size && `el-dropdown-menu--${size}`]" v-show="showPopper">
|
||||
<slot></slot>
|
||||
</ul>
|
||||
</transition>
|
||||
</template>
|
||||
<script>
|
||||
import Popper from 'element-ui/src/utils/vue-popper';
|
||||
|
||||
export default {
|
||||
name: 'ElDropdownMenu',
|
||||
|
||||
componentName: 'ElDropdownMenu',
|
||||
|
||||
mixins: [Popper],
|
||||
|
||||
props: {
|
||||
visibleArrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
arrowOffset: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
size: this.dropdown.dropdownSize
|
||||
};
|
||||
},
|
||||
|
||||
inject: ['dropdown'],
|
||||
|
||||
created() {
|
||||
this.$on('updatePopper', () => {
|
||||
if (this.showPopper) this.updatePopper();
|
||||
});
|
||||
this.$on('visible', val => {
|
||||
this.showPopper = val;
|
||||
});
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.dropdown.popperElm = this.popperElm = this.$el;
|
||||
this.referenceElm = this.dropdown.$el;
|
||||
// compatible with 2.6 new v-slot syntax
|
||||
// issue link https://github.com/ElemeFE/element/issues/14345
|
||||
this.dropdown.initDomOperation();
|
||||
},
|
||||
|
||||
watch: {
|
||||
'dropdown.placement': {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.currentPlacement = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
279
packages/dropdown/src/dropdown.vue
Normal file
279
packages/dropdown/src/dropdown.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<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
|
||||
}
|
||||
},
|
||||
|
||||
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.triggerElm.disabled) return;
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => {
|
||||
this.visible = true;
|
||||
}, this.trigger === 'click' ? 0 : this.showTimeout);
|
||||
},
|
||||
hide() {
|
||||
if (this.triggerElm.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.triggerElm.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 } = this;
|
||||
|
||||
const handleMainButtonClick = (event) => {
|
||||
this.$emit('click', event);
|
||||
hide();
|
||||
};
|
||||
|
||||
let triggerElm = !splitButton
|
||||
? this.$slots.default
|
||||
: (<el-button-group>
|
||||
<el-button type={type} size={dropdownSize} nativeOn-click={handleMainButtonClick}>
|
||||
{this.$slots.default}
|
||||
</el-button>
|
||||
<el-button ref="trigger" type={type} size={dropdownSize} class="el-dropdown__caret-button">
|
||||
<i class="el-dropdown__icon el-icon-arrow-down"></i>
|
||||
</el-button>
|
||||
</el-button-group>);
|
||||
|
||||
return (
|
||||
<div class="el-dropdown" v-clickoutside={hide}>
|
||||
{triggerElm}
|
||||
{this.$slots.dropdown}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
Reference in New Issue
Block a user