فهرست منبع

Menu: move menu popup to body when collapse (#9263)

* change menu popup to body

* add menu-list

* Revert "add menu-list"

This reverts commit 5799df9bf202eb17f7b784be7eb79404fce68e8f.

* fix menu popup

* Update yarn.lock

* Update submenu.vue
baiyaaaaa 7 سال پیش
والد
کامیت
f0f75fb561

+ 1 - 10
packages/menu/src/menu-mixin.js

@@ -1,4 +1,5 @@
 export default {
+  inject: ['rootMenu'],
   computed: {
     indexPath() {
       const path = [this.index];
@@ -11,16 +12,6 @@ export default {
       }
       return path;
     },
-    rootMenu() {
-      let parent = this.$parent;
-      while (
-        parent &&
-        parent.$options.componentName !== 'ElMenu'
-      ) {
-        parent = parent.$parent;
-      }
-      return parent;
-    },
     parentMenu() {
       let parent = this.$parent;
       while (

+ 4 - 0
packages/menu/src/menu.vue

@@ -127,6 +127,9 @@
     computed: {
       hoverBackground() {
         return this.backgroundColor ? this.mixColor(this.backgroundColor, 0.2) : '';
+      },
+      isMenuPopup() {
+        return this.mode === 'horizontal' || (this.mode === 'vertical' && this.collapse);
       }
     },
     watch: {
@@ -140,6 +143,7 @@
 
       collapse(value) {
         if (value) this.openedMenus = [];
+        this.broadcast('ElSubmenu', 'toggle-collapse', value);
       }
     },
     methods: {

+ 133 - 40
packages/menu/src/submenu.vue

@@ -1,53 +1,31 @@
-<template>
-  <li
-    :class="{
-      'el-submenu': true,
-      'is-active': active,
-      'is-opened': opened
-    }"
-    @mouseenter="handleMouseenter"
-    @mouseleave="handleMouseleave"
-    @focus="handleMouseenter"
-    role="menuitem"
-    aria-haspopup="true"
-    :aria-expanded="opened"
-  >
-    <div
-      class="el-submenu__title"
-      ref="submenu-title"
-      @click="handleClick"
-      @mouseenter="handleTitleMouseenter"
-      @mouseleave="handleTitleMouseleave"
-      :style="[paddingStyle, titleStyle, { backgroundColor }]">
-      <slot name="title"></slot>
-      <i :class="{
-        'el-submenu__icon-arrow': true,
-        'el-icon-arrow-down': rootMenu.mode === 'horizontal' || rootMenu.mode === 'vertical' && !rootMenu.collapse,
-        'el-icon-arrow-right': rootMenu.mode === 'vertical' && rootMenu.collapse
-      }">
-      </i>
-    </div>
-    <template v-if="rootMenu.mode === 'horizontal' || (rootMenu.mode === 'vertical' && rootMenu.collapse)">
-      <transition :name="menuTransitionName">
-        <ul class="el-menu" v-show="opened" :style="{ backgroundColor: rootMenu.backgroundColor || '' }" role="menu"><slot></slot></ul>
-      </transition>
-    </template>
-    <el-collapse-transition v-else>
-      <ul class="el-menu" v-show="opened" :style="{ backgroundColor: rootMenu.backgroundColor || '' }" role="menu"><slot></slot></ul>
-    </el-collapse-transition>
-  </li>
-</template>
 <script>
   import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition';
   import menuMixin from './menu-mixin';
   import Emitter from 'element-ui/src/mixins/emitter';
+  import Popper from 'element-ui/src/utils/vue-popper';
+
+  const poperMixins = {
+    props: {
+      transformOrigin: {
+        type: [Boolean, String],
+        default: false
+      },
+      offset: Popper.props.offset,
+      boundariesPadding: Popper.props.boundariesPadding,
+      popperOptions: Popper.props.popperOptions
+    },
+    data: Popper.data,
+    methods: Popper.methods,
+    beforeDestroy: Popper.beforeDestroy,
+    deactivated: Popper.deactivated
+  };
 
   export default {
     name: 'ElSubmenu',
 
     componentName: 'ElSubmenu',
 
-    mixins: [menuMixin, Emitter],
+    mixins: [menuMixin, Emitter, poperMixins],
 
     components: { ElCollapseTransition },
 
@@ -68,12 +46,26 @@
 
     data() {
       return {
+        popperJS: null,
         timeout: null,
         items: {},
         submenus: {}
       };
     },
+    watch: {
+      opened(val) {
+        if (this.isMenuPopup) {
+          this.$nextTick(_ => {
+            this.updatePopper();
+          });
+        }
+      }
+    },
     computed: {
+      // popper option
+      appendToBody() {
+        return this.rootMenu === this.$parent;
+      },
       menuTransitionName() {
         return this.rootMenu.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top';
       },
@@ -114,6 +106,9 @@
       mode() {
         return this.rootMenu.mode;
       },
+      isMenuPopup() {
+        return this.rootMenu.isMenuPopup;
+      },
       titleStyle() {
         if (this.mode !== 'horizontal') {
           return {
@@ -131,6 +126,13 @@
       }
     },
     methods: {
+      handleCollapseToggle(value) {
+        if (value) {
+          this.initPopper();
+        } else {
+          this.doDestroy();
+        }
+      },
       addItem(item) {
         this.$set(this.items, item.index, item);
       },
@@ -188,15 +190,106 @@
         if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
         const title = this.$refs['submenu-title'];
         title && (title.style.backgroundColor = this.rootMenu.backgroundColor || '');
+      },
+      updatePlacement() {
+        this.currentPlacement = this.mode === 'horizontal' ? 'bottom-start' : 'right-start';
+      },
+      initPopper() {
+        this.referenceElm = this.$el;
+        this.popperElm = this.$refs.menu;
+        this.updatePlacement();
       }
     },
     created() {
       this.parentMenu.addSubmenu(this);
       this.rootMenu.addSubmenu(this);
+      this.$on('toggle-collapse', this.handleCollapseToggle);
+    },
+    mounted() {
+      this.initPopper();
     },
     beforeDestroy() {
       this.parentMenu.removeSubmenu(this);
       this.rootMenu.removeSubmenu(this);
+    },
+    render(h) {
+      const {
+        active,
+        opened,
+        paddingStyle,
+        titleStyle,
+        backgroundColor,
+        $slots,
+        rootMenu,
+        currentPlacement,
+        menuTransitionName,
+        mode
+      } = this;
+
+      const popupMenu = (
+        <transition name={menuTransitionName}>
+          <div
+            ref="menu"
+            v-show={opened}
+            class={[`el-menu--${mode}`]}
+            on-mouseenter={this.handleMouseenter}
+            on-mouseleave={this.handleMouseleave}
+            on-focus={this.handleMouseenter}>
+            <ul
+              role="menu"
+              class={['el-menu el-menu--popup', `el-menu--popup-${currentPlacement}`]}
+              style={{ backgroundColor: rootMenu.backgroundColor || '' }}>
+              {$slots.default}
+            </ul>
+          </div>
+        </transition>
+      );
+
+      const inlineMenu = (
+        <el-collapse-transition>
+          <ul
+            role="menu"
+            class="el-menu el-menu--inline"
+            v-show={opened}
+            style={{ backgroundColor: rootMenu.backgroundColor || '' }}>
+            {$slots.default}
+          </ul>
+        </el-collapse-transition>
+      );
+
+      return (
+        <li
+          class={{
+            'el-submenu': true,
+            'is-active': active,
+            'is-opened': opened
+          }}
+          role="menuitem"
+          aria-haspopup="true"
+          aria-expanded={opened}
+          on-mouseenter={this.handleMouseenter}
+          on-mouseleave={this.handleMouseleave}
+          on-focus={this.handleMouseenter}
+        >
+          <div
+            class="el-submenu__title"
+            ref="submenu-title"
+            on-click={this.handleClick}
+            on-mouseenter={this.handleTitleMouseenter}
+            on-mouseleave={this.handleTitleMouseleave}
+            style={[paddingStyle, titleStyle, { backgroundColor }]}
+          >
+            {$slots.title}
+            <i class={{
+              'el-submenu__icon-arrow': true,
+              'el-icon-arrow-down': rootMenu.mode === 'horizontal' || rootMenu.mode === 'vertical' && !rootMenu.collapse,
+              'el-icon-arrow-right': rootMenu.mode === 'vertical' && rootMenu.collapse
+            }}>
+            </i>
+          </div>
+          {this.isMenuPopup ? popupMenu : inlineMenu}
+        </li>
+      );
     }
   };
 </script>

+ 48 - 36
packages/theme-chalk/src/menu.scss

@@ -8,6 +8,7 @@
   font-size: 14px;
   color: $--menu-item-color;
   padding: 0 20px;
+  list-style: none;
   cursor: pointer;
   position: relative;
   transition: border-color .3s, background-color .3s, color .3s;
@@ -24,21 +25,14 @@
   background-color: $--menu-item-fill;
   @include utils-clearfix;
 
-  & li {
-    list-style: none;
-  }
-
   @include m(horizontal) {
     border-right: none;
     border-bottom: solid 1px #e6e6e6;
-    & .el-menu-item {
+    & > .el-menu-item {
       float: left;
       height: 60px;
       line-height: 60px;
       margin: 0;
-      cursor: pointer;
-      position: relative;
-      box-sizing: border-box;
       border-bottom: 2px solid transparent;
       color: $--color-text-secondary;
 
@@ -51,26 +45,22 @@
         background-color: #fff;
       }
     }
-    & .el-submenu {
+    & > .el-submenu {
       float: left;
-      position: relative;
-      &:focus {
+
+      &:focus,
+      &:hover {
         outline: none;
-        > .el-submenu__title {
+        .el-submenu__title {
           color: $--color-text-primary;
         }
       }
-      > .el-menu {
-        position: absolute;
-        top: 65px;
-        left: 0;
-        border: none;
-        padding: 5px 0;
-        background-color: $--color-white;
-        z-index: 100;
-        min-width: 100%;
-        box-shadow: $--box-shadow-light;
-        border-radius: $--border-radius-small;
+
+      &.is-active {
+        .el-submenu__title {
+          border-bottom: 2px solid $--color-primary;
+          color: $--color-text-primary;
+        }
       }
 
       & .el-submenu__title {
@@ -78,35 +68,38 @@
         line-height: 60px;
         border-bottom: 2px solid transparent;
         color: $--color-text-secondary;
-      }
 
-      .el-submenu__title:hover {
-        background-color: #fff;
+        &:hover {
+          background-color: #fff;
+        }
       }
-
+      & .el-submenu__icon-arrow {
+        position: static;
+        vertical-align: middle;
+        margin-left: 8px;
+        margin-top: -3px;
+      }
+    }
+    & .el-menu {
       & .el-menu-item {
         background-color: $--color-white;
         float: none;
         height: 36px;
         line-height: 36px;
         padding: 0 10px;
-      }
+        color: $--color-text-secondary;
 
-      & .el-submenu__icon-arrow {
-        position: static;
-        vertical-align: middle;
-        margin-left: 8px;
-        margin-top: -3px;
+        &.is-active {
+          color: $--color-text-primary;
+        }
       }
     }
     & .el-menu-item:hover,
-    & .el-submenu__title:hover,
     & .el-menu-item:focus {
       outline: none;
       color: $--color-text-primary;
     }
-    & > .el-menu-item.is-active,
-    & > .el-submenu.is-active .el-submenu__title {
+    & > .el-menu-item.is-active {
       border-bottom: 2px solid $--color-primary;
       color: $--color-text-primary;
     }
@@ -162,6 +155,21 @@
       }
     }
   }
+  @include m(popup) {
+    z-index: 100;
+    min-width: 200px;
+    border: none;
+    padding: 5px 0;
+    border-radius: $--border-radius-small;
+    box-shadow: $--box-shadow-light;
+
+    &-bottom-start {
+      margin-top: 5px;
+    }
+    &-right-start {
+      margin-left: 5px;
+    }
+  }
 }
 @include b(menu-item) {
   @include menu-item;
@@ -198,6 +206,10 @@
 }
   
 @include b(submenu) {
+  list-style: none;
+  margin: 0;
+  padding-left: 0;
+
   @include e(title) {
     position: relative;
     @include menu-item;

+ 8 - 1
src/utils/vue-popper.js

@@ -16,6 +16,10 @@ const stop = e => e.stopPropagation();
  */
 export default {
   props: {
+    transformOrigin: {
+      type: [Boolean, String],
+      default: true
+    },
     placement: {
       type: String,
       default: 'bottom'
@@ -139,6 +143,7 @@ export default {
     },
 
     resetTransformOrigin() {
+      if (!this.transformOrigin) return;
       let placementMap = {
         top: 'bottom',
         bottom: 'top',
@@ -147,7 +152,9 @@ export default {
       };
       let placement = this.popperJS._popper.getAttribute('x-placement').split('-')[0];
       let origin = placementMap[placement];
-      this.popperJS._popper.style.transformOrigin = ['top', 'bottom'].indexOf(placement) > -1 ? `center ${ origin }` : `${ origin } center`;
+      this.popperJS._popper.style.transformOrigin = typeof this.transformOrigin === 'string'
+        ? this.transformOrigin
+        : ['top', 'bottom'].indexOf(placement) > -1 ? `center ${ origin }` : `${ origin } center`;
     },
 
     appendArrow(element) {

+ 3 - 3
test/unit/specs/menu.spec.js

@@ -270,7 +270,7 @@ describe('Menu', () => {
     var submenu = vm.$refs.submenu;
     triggerEvent(submenu.$el, 'mouseenter');
     setTimeout(_ => {
-      expect(submenu.$el.querySelector('.el-menu').style.display).to.not.ok;
+      expect(document.body.querySelector('.el-menu--popup').parentElement.style.display).to.not.ok;
       done();
     }, 500);
   });
@@ -301,10 +301,10 @@ describe('Menu', () => {
     triggerElm.click();
 
     setTimeout(_ => {
-      expect(submenu.$el.querySelector('.el-menu').style.display).to.not.ok;
+      expect(document.body.querySelector('.el-menu--popup').parentElement.style.display).to.not.ok;
       triggerElm.click();
       setTimeout(_ => {
-        expect(submenu.$el.querySelector('.el-menu').style.display).to.be.equal('none');
+        expect(document.body.querySelector('.el-menu--popup').parentElement.style.display).to.be.equal('none');
         done();
       }, 1000);
     }, 500);