|
@@ -4,6 +4,7 @@
|
|
|
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',
|
|
@@ -61,25 +62,43 @@
|
|
|
return {
|
|
|
timeout: null,
|
|
|
visible: false,
|
|
|
- triggerElm: null
|
|
|
+ triggerElm: null,
|
|
|
+ menuItems: null,
|
|
|
+ menuItemsArray: null,
|
|
|
+ dropdownElm: null,
|
|
|
+ focusing: false
|
|
|
};
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
dropdownSize() {
|
|
|
return this.size || (this.$ELEMENT || {}).size;
|
|
|
+ },
|
|
|
+ listId() {
|
|
|
+ return `dropdown-menu-${generateId()}`;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
mounted() {
|
|
|
this.$on('menu-item-click', this.handleMenuItemClick);
|
|
|
this.initEvent();
|
|
|
+ this.initAria();
|
|
|
},
|
|
|
|
|
|
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', '');
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
|
|
@@ -100,6 +119,8 @@
|
|
|
},
|
|
|
hide() {
|
|
|
if (this.triggerElm.disabled) return;
|
|
|
+ this.removeTabindex();
|
|
|
+ this.resetTabindex(this.triggerElm);
|
|
|
clearTimeout(this.timeout);
|
|
|
this.timeout = setTimeout(() => {
|
|
|
this.visible = false;
|
|
@@ -109,18 +130,98 @@
|
|
|
if (this.triggerElm.disabled) return;
|
|
|
this.visible = !this.visible;
|
|
|
},
|
|
|
+ handleTriggerKeyDown(ev) {
|
|
|
+ const keyCode = ev.keyCode;
|
|
|
+ if ([38, 40].includes(keyCode)) { // 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].includes(keyCode)) { // tab || esc
|
|
|
+ this.hide();
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ },
|
|
|
+ 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].includes(keyCode)) { // 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.triggerElm.focus();
|
|
|
+ target.click();
|
|
|
+ if (!this.hideOnClick) { // click关闭
|
|
|
+ this.visible = false;
|
|
|
+ }
|
|
|
+ } else if ([9, 27].includes(keyCode)) { // tab // esc
|
|
|
+ this.hide();
|
|
|
+ this.triggerElm.focus();
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ },
|
|
|
+ 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);
|
|
|
+ this.menuItems = this.dropdownElm.querySelectorAll("[tabindex='-1']");
|
|
|
+ this.menuItemsArray = Array.prototype.slice.call(this.menuItems);
|
|
|
+
|
|
|
+ if (!this.splitButton) { // 自定义
|
|
|
+ this.triggerElm.setAttribute('role', 'button');
|
|
|
+ this.triggerElm.setAttribute('tabindex', '0');
|
|
|
+ this.triggerElm.className += ' el-dropdown-selfdefine'; // 控制
|
|
|
+ }
|
|
|
+ },
|
|
|
initEvent() {
|
|
|
- let { trigger, show, hide, handleClick, splitButton } = this;
|
|
|
+ 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.$slots.dropdown[0].elm;
|
|
|
+
|
|
|
+ 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);
|
|
|
-
|
|
|
- let dropdownElm = this.$slots.dropdown[0].elm;
|
|
|
-
|
|
|
dropdownElm.addEventListener('mouseenter', show);
|
|
|
dropdownElm.addEventListener('mouseleave', hide);
|
|
|
} else if (trigger === 'click') {
|